2022년 2월 19일 토요일

Java Swing -- Screen Capture

 Java Swing -- Screen Capture

package jlib5.demo;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class ScreenCapture {
    public static void main(String[] args) {
        JFrame capture = new JFrame();
        capture.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Toolkit kit = Toolkit.getDefaultToolkit();
        final Dimension d = kit.getScreenSize();
        capture.setSize(d);

        Rectangle rect = new Rectangle(d);
        try {
            Robot robot = new Robot();
            final BufferedImage image = robot.createScreenCapture(rect);
            image.flush();
            JPanel panel = new JPanel() {
                    private static final long serialVersionUID = 1L;
                    public void paintComponent(Graphics g) {
                            g.drawImage(image, 0, 0, d.width, d.height, this);
                    }
            };
            panel.setOpaque(false);
            panel.prepareImage(image, panel);
            panel.repaint();
            capture.getContentPane().add(panel);
        } catch (Exception e) {
            e.printStackTrace();
        }
        capture.setVisible(true);
    }
}

Java Swing - Toast

 Java Swing - Toast

import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagLayout;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.RoundRectangle2D;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class Toast {
    public static void main(String[] args) throws InterruptedException {
        ToastMessage toast = new ToastMessage("Toast Demo");
        toast.show();
        toast.showToMousePos();

        JFrame frame = new JFrame();
        frame.setSize(300, 300);
        frame.setLocation(600,600);
        frame.setVisible(true);
        toast.showToCenter(frame);
        
        toast.close();
    }
}

class ToastMessage {
    JFrame frame;
    public ToastMessage(final String message) {
        frame = new JFrame();
        frame.setUndecorated(true);
        frame.setLayout(new GridBagLayout());
        frame.setBackground(new Color(240,240,240,250));
        frame.setLocationRelativeTo(null);
        frame.setSize(300, 50);
        frame.add(new JLabel(message));
        frame.addComponentListener(new ComponentAdapter() {
           @Override
           public void componentResized(ComponentEvent e) {
              frame.setShape(new  RoundRectangle2D.Double(0,0,frame.getWidth(),frame.getHeight(), 20, 20));                      
           }
        });        
    }

    public void show() {
        try {
            frame.setOpacity(1);
            frame.setVisible(true);
            Thread.sleep(1000);
            for (double d = 1.0; d > 0.2; d -= 0.1) {
               Thread.sleep(100);
               frame.setOpacity((float)d);
            }
            frame.setVisible(false);
        }catch (Exception ignore) {
        }
    }
    
    public void show(int x, int y) {
        frame.setLocation(x, y);
        show();
    }
    
    public void showToMousePos() {
        Point p = MouseInfo.getPointerInfo().getLocation();
        show( p.x, p.y );
    }
    
    public synchronized void showToCenter(Component comp) {
        frame.setLocationRelativeTo(SwingUtilities.getRoot(comp));
        show();
    }
    
    public void close() {
        frame.dispose();
    }
}

Java Swing - Notification

 Java Swing - Notification


package com.backup.swing;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;

public class Notification {
	public static void show(String title, String message) {
		JFrame frame = new JFrame(); 
		frame.setUndecorated(true);
		frame.setSize(300,125); 
		frame.setAlwaysOnTop(true);
		frame.setLayout(new GridBagLayout()); 
		
		Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();// size of the screen 
		Insets toolHeight = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());// height of the task bar 
		frame.setLocation(scrSize.width - frame.getWidth(), scrSize.height - toolHeight.bottom - frame.getHeight()); 
		
		GridBagConstraints constraints = new GridBagConstraints(); 
		constraints.gridx = 0; 
		constraints.gridy = 0; 
		constraints.weightx = 1.0f; 
		constraints.weighty = 1.0f; 
		constraints.insets = new Insets(5, 5, 5, 5); 
		constraints.fill = GridBagConstraints.BOTH; 
		JLabel headingLabel = new JLabel(title); 
		headingLabel.setIcon(new ImageIcon("/res/About32.png")); 
		headingLabel.setOpaque(false); 
		frame.add(headingLabel, constraints); 
		constraints.gridx++; 
		constraints.weightx = 0f; 
		constraints.weighty = 0f; 
		constraints.fill = GridBagConstraints.NONE; 
		constraints.anchor = GridBagConstraints.NORTH; 
		JButton closeButton = new JButton( new AbstractAction() {
	        @Override public void actionPerformed(final ActionEvent e) { 
	               frame.dispose(); 
	        }});
		closeButton.setText("X");
		closeButton.setMargin(new Insets(1, 4, 1, 4)); 
		closeButton.setFocusable(false); 
		frame.add(closeButton, constraints); 
		constraints.gridx = 0; 
		constraints.gridy++; 
		constraints.weightx = 1.0f; 
		constraints.weighty = 1.0f; 
		constraints.insets = new Insets(5, 5, 5, 5); 
		constraints.fill = GridBagConstraints.BOTH; 
		JLabel messageLabel = new JLabel("<html>"+message); 
		//messageLabel.setBackground(Color.YELLOW);
		frame.add(messageLabel, constraints); 
		frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		//frame.setBackground(Color.YELLOW);
		frame.setVisible(true); 
		
		new Thread(){ 
		      @Override 
		      public void run() { 
		           try { 
		                  Thread.sleep(5000); // time after which pop up will be disappeared. 
		                  frame.dispose(); 
		           } catch (InterruptedException e) { 
		                  e.printStackTrace(); 
		           } 
		      }; 
		}.start(); 
		
	}
	
	public static void main(String[] args) {
		String title = "This is header of notification message"; 
		String message = "You got a new notification message. Isn't it awesome to have such a notification message."; 
		Notification.show(title, message);
	}
}

Java Swing - LuaApi

 Java Swing - LuaApi


package jlib5.java;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class LuaApi {

    ScriptEngineManager sem ;
    ScriptEngine e;
    ScriptEngineFactory f;

    public LuaApi() {
        sem = new ScriptEngineManager();
        e = sem.getEngineByName("luaj");
        f = e.getFactory();
    }

    public void showVersion() {
        System.out.println( "Engine name: " +f.getEngineName() );
        System.out.println( "Engine Version: " +f.getEngineVersion() );
        System.out.println( "LanguageName: " +f.getLanguageName() );
        System.out.println( "Language Version: " +f.getLanguageVersion() );
    }

    public void set(String name, int value ) {
        e.put( name, value );
    }

    public Object get(String name) {
        return e.get( name );
    }

    public Object eval(String statement ) {
        try {
            return e.eval(statement);
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
        return null;
    }


    public boolean checkCondition(String statement) {
        String lua_statement = "if " + statement + " then rv=1 else rv=0 end";
        eval( lua_statement );
        int rv = (int)get("rv");
        return rv != 0;
    }

    public boolean checkJavaCondition(String statement) {
        String lua_statement = statement.replace("&&", " and " )
                .replace( "~=", "!=")
                .replace( "||", " or ");
        return checkCondition(lua_statement);
    }

    public static void main(String[] args) {
        LuaApi lua = new LuaApi();
        lua.showVersion();
        lua.set("a",1);
        lua.set("b",2);
        System.out.println( lua.checkCondition("a == 1 and b == 2") );
        System.out.println( lua.checkCondition("a == 1 and b == 1") );
    }
}

Java Swing - PUmlApi

 Java Swing - PUmlApi


package jlib5.java;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import net.sourceforge.plantuml.BlockUml;
import net.sourceforge.plantuml.BlockUmlBuilder;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.Option;
import net.sourceforge.plantuml.core.Diagram;

public class PUmlApi {

    public static List<BufferedImage> makeImage(String input) throws IOException, InterruptedException {
        @SuppressWarnings("deprecation")
        BlockUmlBuilder builder = new BlockUmlBuilder(
                        new ArrayList<String>(), //option.getConfig(),  
                        "UTF-8",      //option.getCharset(), 
                        new Option().getDefaultDefines(), 
                        new StringReader(input));      

        List<BufferedImage> result = new ArrayList<>();
        
        for( BlockUml blockUml : builder.getBlockUmls() ) {
            try {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                final Diagram diagram = blockUml.getDiagram();
                diagram.exportDiagram((OutputStream)os, 0, new FileFormatOption(FileFormat.PNG));
                result.add(ImageIO.read(new ByteArrayInputStream(os.toByteArray())));
                os.close();
            } catch (Exception e) {
                e.getSuppressed();
            }
        }     
        return result;
    }
}

Java Swing - Tree

 Java Swing - Tree


package jlib5.swing;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

public class Tree {

    public JTree ctrl;
    JPopupMenu menu;

    public Tree(String rootName, Runnable handler) {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(rootName);
        /* root.add( new DefaultMutableTreeNode("하위1") );
        root.add( new DefaultMutableTreeNode("하위2") ); */
        ctrl = new JTree(root);
        if( handler != null ) {
            setTreeHandler(handler);
        }
    }
    public void showTreeRoot() {
        ctrl.setRootVisible(true);}
    public void hideTreeRoot() {
        ctrl.setRootVisible(false);}
    public void expandAll() {
        for (int i = 0; i < ctrl.getRowCount(); i++) {
            ctrl.expandRow(i);
        }
    }
    public void clearAll() {
        ctrl.getSelectionModel().clearSelection();
        DefaultTreeModel model = (DefaultTreeModel) ctrl.getModel();
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
        root.removeAllChildren();
        model.reload();
        //model.setRoot(null);
    }
    public void clear(DefaultMutableTreeNode node) {
        DefaultTreeModel model = (DefaultTreeModel) ctrl.getModel();
        node.removeAllChildren();
        model.reload();
        //model.setRoot(null);
    }

    public void addContextMenu(String name, Runnable handler) {
        JMenuItem item = new JMenuItem(name);
        item.addActionListener( (e) -> handler.run() );
        if( this.menu == null ) {
            this.menu = new JPopupMenu();
            this.menu.add(item);
            ctrl.setComponentPopupMenu(this.menu);
        } else {
            menu.add(item);
        }
    }

    public void setTreeHandler(Runnable runnable) {
        ctrl.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener(){
            public void valueChanged(TreeSelectionEvent event) {
                runnable.run();
            }
        });
    }
    public String getTreeSelectionPath() {
        return ctrl.getSelectionModel().getSelectionPath().toString();
    }
    public DefaultMutableTreeNode addTreeToRoot(String name) {
        DefaultTreeModel model = (DefaultTreeModel) ctrl.getModel();
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(name);
        model.insertNodeInto(node, root, root.getChildCount());
        //model.insertNodeInto(node, root, model.getChildCount(root));
        model.reload(root);
        return node;
    }
    public DefaultMutableTreeNode addTree(DefaultMutableTreeNode parent, String name) {
        DefaultTreeModel model = (DefaultTreeModel) ctrl.getModel();
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(name);
        model.insertNodeInto(node, parent, parent.getChildCount());
        model.reload(parent);
        return node;
    }
    public DefaultMutableTreeNode getSelectedNode() {
        return (DefaultMutableTreeNode)ctrl.getLastSelectedPathComponent();
    }
    public String[] getSelectedTreePath() {
        TreePath path = ctrl.getSelectionPath();
        if( path != null ) {
            int count = path.getPathCount();
            String[] nodes = new String[count];
            for( int i = 0; i < count; i++ ) {
                nodes[count-i-1] = path.getLastPathComponent().toString();
                path = path.getParentPath();
            }
            return nodes;
        }
        return null;
    }


    public String getTreeToString(DefaultMutableTreeNode node, String prefix) {
        StringBuilder sb = new StringBuilder();
        if( prefix == null ) {
            prefix = "";
            sb.append(node.toString()).append("\n|  \n");
        }
        if( node.getChildCount() == 0 ) {
            return prefix + node.toString() + "\n";
        } else {
            int num = node.getChildCount();
            for (int i = 0; i < num; i++) {
                DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
                if( i == num-1 ) {
                    sb.append(prefix).append("`--").append(child).append("\n");
                    if (child.getChildCount() > 0) {
                        sb.append(getTreeToString(child, prefix + "   "));
                    }
                } else {
                    sb.append(prefix).append("|--").append(child).append("\n");
                    if (child.getChildCount() > 0) {
                        sb.append(getTreeToString(child, prefix + "|  "));
                    }
                }
            }
            return sb.toString();
        }
    }

}

Java Swing - TextView

 Java Swing - TextView


package jlib5.swing;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import javax.swing.undo.UndoManager;

public class TextView {

	public JTextPane textPane;
	public JScrollPane ctrl;
	public LineNumber lineNumber;
	JPopupMenu menu;

	public TextView() {
		textPane = new JTextPane();
		lineNumber = new LineNumber(textPane);
		ctrl = new JScrollPane(textPane);
		ctrl.setRowHeaderView(lineNumber);
		setContextMenu();
	}
	public TextView(String fontName, int fontSize) {
		this();
		textPane.setFont(new Font( fontName, Font.PLAIN, fontSize)); //Font.BOLD
	}

        public JScrollPane getLayout() { return ctrl; }
        public JTextPane getCtrl() { return textPane; }
        
	public void addContextMenu(String name, Runnable handler) {
            if( name == null ) {
                menu.addSeparator();
            } else {
		JMenuItem item = new JMenuItem(name);
		item.addActionListener( (e) -> handler.run() );
		if( this.menu == null ) {
                    this.menu = new JPopupMenu();
                    this.menu.add(item);
                    textPane.setComponentPopupMenu(this.menu);
		} else {
                    menu.add(item);
                }
            }
	}
	public void pasteHtml() {
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            if( clipboard != null ) {
                try {
                        Object htmlText = clipboard.getData(DataFlavor.allHtmlFlavor);
                        clipboard.setContents(new StringSelection(htmlText.toString()), null);
                        textPane.paste();
                } catch (Exception e) {
                        e.printStackTrace();
                }
            }
	}
	public void setContextMenu() {
            addContextMenu( "Clear", () -> textPane.setText("") );
            addContextMenu( "Cut", () -> textPane.cut() );
            addContextMenu( "Copy", () -> textPane.copy() );
            addContextMenu( "Paste", () -> textPane.paste() );
            addContextMenu( "Paste Html", this::pasteHtml );
            addContextMenu( null, null );
              
	}

	public JTextPane getTextPane() { return textPane; }
	public JScrollPane getScrollPane() { return ctrl; }
	public void clear() {
		textPane.setText("");
	}
	public void disableEdit() { textPane.setEditable(false); }
	public void enableEdit() { textPane.setEditable(true); }
	public void select( int start, int end ) { textPane.select(start, end); }
	public void append(String text) throws BadLocationException {
		StyledDocument doc = textPane.getStyledDocument();
		doc.insertString(doc.getLength(), text, null);
	}
	public void scrollToTop() {
		Swing.runLater(() -> getScrollPane().getVerticalScrollBar().setValue(0));
	}
	public void scrollToBottom() {
		StyledDocument doc = textPane.getStyledDocument();
		int pos = doc.getLength();
		Swing.runLater(() -> getScrollPane().getVerticalScrollBar().setValue(pos-1));
	}

	public static class LineNumber extends JPanel
	implements CaretListener, DocumentListener, PropertyChangeListener
	{
		private static final long serialVersionUID = 1L;
		public final static float LEFT = 0.0f;
		public final static float CENTER = 0.5f;
		public final static float RIGHT = 1.0f;
	
		private final Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
	
		private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
	
		//  Text component this TextTextLineNumber component is in sync with
	
		private JTextComponent component;
	
		//  Properties that can be changed
	
		private boolean updateFont;
		private int borderGap;
		private Color currentLineForeground;
		private float digitAlignment;
		private int minimumDisplayDigits;
	
		//  Keep history information to reduce the number of times the component
		//  needs to be repainted
	
	    private int lastDigits;
	    private int lastHeight;
	    private int lastLine;
	
		private HashMap<String, FontMetrics> fonts;
	
		/**
		 *	Create a line number component for a text component. This minimum
		 *  display width will be based on 3 digits.
		 *
		 *  @param component  the related text component
		 */
		public LineNumber(JTextComponent component)
		{
			this(component, 3);
		}
	
		public LineNumber(JTextComponent component, int minimumDisplayDigits)
		{
			this.component = component;
	
			setFont( component.getFont() );
	
			setBorderGap( 5 );
			setCurrentLineForeground( Color.RED );
			setDigitAlignment( RIGHT );
			setMinimumDisplayDigits( minimumDisplayDigits );
	
			component.getDocument().addDocumentListener(this);
			component.addCaretListener( this );
			component.addPropertyChangeListener("font", this);
		}
	
		public boolean getUpdateFont()
		{
			return updateFont;
		}
	
		public void setUpdateFont(boolean updateFont)
		{
			this.updateFont = updateFont;
		}
	
		public int getBorderGap()
		{
			return borderGap;
		}
	
		public void setBorderGap(int borderGap)
		{
			this.borderGap = borderGap;
			Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
			setBorder( new CompoundBorder(OUTER, inner) );
			lastDigits = 0;
			setPreferredWidth();
		}
	
		public Color getCurrentLineForeground()
		{
			return currentLineForeground == null ? getForeground() : currentLineForeground;
		}
	
		public void setCurrentLineForeground(Color currentLineForeground)
		{
			this.currentLineForeground = currentLineForeground;
		}
	
		public float getDigitAlignment()
		{
			return digitAlignment;
		}
	
		public void setDigitAlignment(float digitAlignment)
		{
			this.digitAlignment =
				digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
		}
	
		public int getMinimumDisplayDigits()
		{
			return minimumDisplayDigits;
		}
	
		public void setMinimumDisplayDigits(int minimumDisplayDigits)
		{
			this.minimumDisplayDigits = minimumDisplayDigits;
			setPreferredWidth();
		}
	
		private void setPreferredWidth()
		{
			Element root = component.getDocument().getDefaultRootElement();
			int lines = root.getElementCount();
			int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
	
			//  Update sizes when number of digits in the line number changes
	
			if (lastDigits != digits)
			{
				lastDigits = digits;
				FontMetrics fontMetrics = getFontMetrics( getFont() );
				int width = fontMetrics.charWidth( '0' ) * digits;
				Insets insets = getInsets();
				int preferredWidth = insets.left + insets.right + width;
	
				Dimension d = getPreferredSize();
				d.setSize(preferredWidth, HEIGHT);
				setPreferredSize( d );
				setSize( d );
			}
		}
	
		/**
		 *  Draw the line numbers
		 */
		@SuppressWarnings("deprecation")
		@Override
		public void paintComponent(Graphics g)
		{
			super.paintComponent(g);
	
			FontMetrics fontMetrics = component.getFontMetrics( component.getFont() );
			Insets insets = getInsets();
			int availableWidth = getSize().width - insets.left - insets.right;
	
			Rectangle clip = g.getClipBounds();
			int rowStartOffset = component.viewToModel( new Point(0, clip.y) );
			int endOffset = component.viewToModel( new Point(0, clip.y + clip.height) );
	
			while (rowStartOffset <= endOffset)
			{
				try
	            {
	    			if (isCurrentLine(rowStartOffset))
	    				g.setColor( getCurrentLineForeground() );
	    			else
	    				g.setColor( getForeground() );
	
	    			String lineNumber = getTextLineNumber(rowStartOffset);
	    			int stringWidth = fontMetrics.stringWidth( lineNumber );
	    			int x = getOffsetX(availableWidth, stringWidth) + insets.left;
					int y = getOffsetY(rowStartOffset, fontMetrics);
	    			g.drawString(lineNumber, x, y);
	
	    			rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
				}
				catch(Exception e) {break;}
			}
		}
	
		private boolean isCurrentLine(int rowStartOffset)
		{
			int caretPosition = component.getCaretPosition();
			Element root = component.getDocument().getDefaultRootElement();
	
			if (root.getElementIndex( rowStartOffset ) == root.getElementIndex(caretPosition))
				return true;
			else
				return false;
		}
	
		protected String getTextLineNumber(int rowStartOffset)
		{
			Element root = component.getDocument().getDefaultRootElement();
			int index = root.getElementIndex( rowStartOffset );
			Element line = root.getElement( index );
	
			if (line.getStartOffset() == rowStartOffset)
				return String.valueOf(index + 1);
			else
				return "";
		}
	
		private int getOffsetX(int availableWidth, int stringWidth)
		{
			return (int)((availableWidth - stringWidth) * digitAlignment);
		}
	
		private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
			throws BadLocationException
		{
			Rectangle r = component.modelToView( rowStartOffset );
			int lineHeight = fontMetrics.getHeight();
			int y = r.y + r.height;
			int descent = 0;
	
			if (r.height == lineHeight)  // default font is being used
			{
				descent = fontMetrics.getDescent();
			}
			else  // We need to check all the attributes for font changes
			{
				if (fonts == null)
					fonts = new HashMap<String, FontMetrics>();
	
				Element root = component.getDocument().getDefaultRootElement();
				int index = root.getElementIndex( rowStartOffset );
				Element line = root.getElement( index );
	
				for (int i = 0; i < line.getElementCount(); i++)
				{
					Element child = line.getElement(i);
					AttributeSet as = child.getAttributes();
					String fontFamily = (String)as.getAttribute(StyleConstants.FontFamily);
					Integer fontSize = (Integer)as.getAttribute(StyleConstants.FontSize);
					String key = fontFamily + fontSize;
	
					FontMetrics fm = fonts.get( key );
	
					if (fm == null)
					{
						Font font = new Font(fontFamily, Font.PLAIN, fontSize);
						fm = component.getFontMetrics( font );
						fonts.put(key, fm);
					}
	
					descent = Math.max(descent, fm.getDescent());
				}
			}
	
			return y - descent;
		}
	
		//
		//  Implement CaretListener interface
		//
		@Override
		public void caretUpdate(CaretEvent e)
		{
			int caretPosition = component.getCaretPosition();
			Element root = component.getDocument().getDefaultRootElement();
			int currentLine = root.getElementIndex( caretPosition );
			if (lastLine != currentLine)
			{
	//			repaint();
				getParent().repaint();
				lastLine = currentLine;
			}
		}
	
		//
		//  Implement DocumentListener interface
		//
		@Override
		public void changedUpdate(DocumentEvent e)
		{
			documentChanged();
		}
	
		@Override
		public void insertUpdate(DocumentEvent e)
		{
			documentChanged();
		}
	
		@Override
		public void removeUpdate(DocumentEvent e)
		{
			documentChanged();
		}
	
		private void documentChanged()
		{
			SwingUtilities.invokeLater(new Runnable()
			{
				@SuppressWarnings("deprecation")
				@Override
				public void run()
				{
					try
					{
						int endPos = component.getDocument().getLength();
						Rectangle rect = component.modelToView(endPos);
	
						if (rect != null && rect.y != lastHeight)
						{
							setPreferredWidth();
	//						repaint();
							getParent().repaint();
							lastHeight = rect.y;
						}
					}
					catch (BadLocationException ex) { /* nothing to do */ }
				}
			});
		}
	
		@Override
		public void propertyChange(PropertyChangeEvent evt)
		{
			if (evt.getNewValue() instanceof Font)
			{
				if (updateFont)
				{
					Font newFont = (Font) evt.getNewValue();
					setFont(newFont);
					lastDigits = 0;
					setPreferredWidth();
				}
				else
				{
	//				repaint();
					getParent().repaint();
				}
			}
		}
	}	
}