/*
 * Created on 13 juil. 2005
 *
 */
package bordel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.MenuSelectionManager;
import javax.swing.UIManager;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.LineBorder;
import javax.swing.border.MatteBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.rtf.RTFEditorKit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

/**
 * Récuperé tel quel ! (ou presque)
 * @author lefevre
 */
public class WordProcessor extends JFrame {
	protected JTextPane m_monitor;
	protected JMenuBar menuBar;
	protected JScrollPane conteneur;
	protected StyleContext m_context;
	protected DefaultStyledDocument m_doc;
	protected RTFEditorKit m_kit;
	protected JFileChooser m_chooser;
	protected SimpleFilter m_rtfFilter;
	protected JToolBar m_toolBar;
	protected JComboBox m_cbFonts;
	protected JComboBox m_cbSizes;
	protected SmallToggleButton m_bBold;
	protected SmallToggleButton m_bItalic;
	protected SimpleFilter m_jpgFilter;
	protected SimpleFilter m_gifFilter;
	protected UndoManager m_undo = new UndoManager();
	protected Action m_undoAction;
	protected Action m_redoAction;
	protected ColorMenu m_foreground;
	protected ColorMenu m_background;
	protected String m_fontName = "";
	protected int m_fontSize = 0;
	protected boolean m_skipUpdate;
	protected String[] m_fontNames;
	protected ParagraphDialog m_paragraphDialog;
	protected String[] m_fontSizes;
	protected FontDialog m_fontDialog;
	protected int m_xStart = -1;
	protected int m_xFinish = -1;
	protected JComboBox m_cbStyles;
	
	class SimpleFilter extends FileFilter {
		private String m_description = null;
		private String m_extension = null;
		
		public SimpleFilter(String extension, String description) {
			m_description = description;
			m_extension = "." + extension.toLowerCase();
		}
		
		public String getDescription() {
			return m_description;
		}
		
		public boolean accept(File f) {
			if (f == null) return false;
			if (f.isDirectory()) return true;
			return f.getName().toLowerCase().endsWith(m_extension);
		}
	}
	
	class SmallButton extends JButton implements MouseListener {
		protected Border m_raised;
		protected Border m_lowered;
		protected Border m_inactive;
		
		public SmallButton(Action act, String tip) {
			super((Icon)act.getValue(Action.SMALL_ICON));
			m_raised = new BevelBorder(BevelBorder.RAISED);
			m_lowered = new BevelBorder(BevelBorder.LOWERED);
			m_inactive = new EmptyBorder(2, 2, 2, 2);
			setBorder(m_inactive);
			setMargin(new Insets(1,1,1,1));
			setToolTipText(tip);
			addActionListener(act);
			addMouseListener(this);
			setRequestFocusEnabled(false);
		}
		
		public float getAlignmentY() { return 0.5f; }
		
		public void mousePressed(MouseEvent e) {
			setBorder(m_lowered);
		}
		
		public void mouseReleased(MouseEvent e) {
			setBorder(m_inactive);
		}
		
		public void mouseClicked(MouseEvent e) {}
		
		public void mouseEntered(MouseEvent e) {
			setBorder(m_raised);
		}
		
		public void mouseExited(MouseEvent e) {
			setBorder(m_inactive);
		}
	}
	
	class SmallToggleButton extends JToggleButton implements ItemListener {
		protected Border m_raised;
		protected Border m_lowered;
		
		public SmallToggleButton(boolean selected, ImageIcon imgUnselected, ImageIcon imgSelected, String tip) {
			super(imgUnselected, selected);
			setHorizontalAlignment(CENTER);
			setBorderPainted(true);
			m_raised = new BevelBorder(BevelBorder.RAISED);
			m_lowered = new BevelBorder(BevelBorder.LOWERED);
			setBorder(selected ? m_lowered : m_raised);
			setMargin(new Insets(1,1,1,1));
			setToolTipText(tip);
			setRequestFocusEnabled(false);
			setSelectedIcon(imgSelected);
			addItemListener(this);
		}
		
		public float getAlignmentY() { return 0.5f; }
		
		public void itemStateChanged(ItemEvent e) {
			setBorder(isSelected() ? m_lowered : m_raised);
		}
	}
	
	class DialogLayout implements LayoutManager {
	  protected int m_divider = -1;
	  protected int m_hGap = 10;
	  protected int m_vGap = 5;

	  public DialogLayout() {}

	  public DialogLayout(int hGap, int vGap) {
	    m_hGap = hGap;
	    m_vGap = vGap;
	  }

	  public void addLayoutComponent(String name, Component comp) {}

	  public void removeLayoutComponent(Component comp) {}

	  public Dimension preferredLayoutSize(Container parent) {
	    int divider = getDivider(parent);
	    int w = 0;
	    int h = 0;
	    for (int k=1 ; k<parent.getComponentCount(); k+=2) {
	      Component comp = parent.getComponent(k);
	      Dimension d = comp.getPreferredSize();
	      w = Math.max(w, d.width);
	      h += d.height + m_vGap;
	    }
	    h -= m_vGap;

	    Insets insets = parent.getInsets();
	    return new Dimension(divider+w+insets.left+insets.right, h+insets.top+insets.bottom);
	  }

	  public Dimension minimumLayoutSize(Container parent) {
	    return preferredLayoutSize(parent);
	  }

	  public void layoutContainer(Container parent) {
	    int divider = getDivider(parent);
	    Insets insets = parent.getInsets();
	    int w = parent.getWidth() - insets.left - insets.right - divider;
	    int x = insets.left;
	    int y = insets.top;

	    for (int k=1 ; k<parent.getComponentCount(); k+=2) {
	      Component comp1 = parent.getComponent(k-1);
	      Component comp2 = parent.getComponent(k);
	      Dimension d = comp2.getPreferredSize();
	      
	      comp1.setBounds(x, y, divider-m_hGap, d.height);
	      comp2.setBounds(x+divider, y, w, d.height);
	      y += d.height + m_vGap;
	    }
	  }

	  public int getHGap() { return m_hGap; }

	  public int getVGap() { return m_vGap; }

	  public void setDivider(int divider) {
	    if (divider > 0)
	      m_divider = divider;
	  }

	  public int getDivider() { return m_divider; }

	  protected int getDivider(Container parent) {
	    if (m_divider > 0)
	      return m_divider;

	    int divider = 0;
	    for (int k=0 ; k<parent.getComponentCount(); k+=2) {
	      Component comp = parent.getComponent(k);
	      Dimension d = comp.getPreferredSize();
	      divider = Math.max(divider, d.width);
	    }
	    divider += m_hGap;
	    return divider;
	  }

	  public String toString() {
	    return getClass().getName() + "[hgap=" + m_hGap + ",vgap="+ m_vGap + ",divider=" + m_divider + "]";
	  }
	}
	
	class ColorMenu extends JMenu {
		protected Border m_unselectedBorder;
		protected Border m_selectedBorder;
		protected Border m_activeBorder;
		protected Hashtable m_panes;
		protected ColorPane m_selected;
		
		public ColorMenu(String name) {
			super(name);
			m_unselectedBorder = new CompoundBorder(new MatteBorder(1, 1, 1, 1, getBackground()),new BevelBorder(BevelBorder.LOWERED, Color.white, Color.gray));
			m_selectedBorder = new CompoundBorder(new MatteBorder(2, 2, 2, 2, Color.red), new MatteBorder(1, 1, 1, 1, getBackground()));
			m_activeBorder = new CompoundBorder(new MatteBorder(2, 2, 2, 2, Color.blue), new MatteBorder(1, 1, 1, 1, getBackground()));
			JPanel p = new JPanel();
			p.setBorder(new EmptyBorder(5, 5, 5, 5));
			p.setLayout(new GridLayout(8, 8));
			m_panes = new Hashtable();
			int[] values = new int[] { 0, 128, 192, 255 };
			for (int r=0; r<values.length; r++) {
				for (int g=0; g<values.length; g++) {
					for (int b=0; b<values.length; b++) {
						Color c = new Color(values[r], values[g], values[b]);
						ColorPane pn = new ColorPane(c);
						p.add(pn);
						m_panes.put(c, pn);
					}
				}
			}
			add(p);
		}
		
		public void setColor(Color c) {
			Object obj = m_panes.get(c);
			if (obj == null) return;
			if (m_selected != null) m_selected.setSelected(false);
			m_selected = (ColorPane)obj;
			m_selected.setSelected(true);
		}
		
		public Color getColor() {
			if (m_selected == null) return null;
			return m_selected.getColor();
		}
		
		public void doSelection() {
			fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand()));
		}
		
		class ColorPane extends JPanel implements MouseListener {
			protected Color m_c;
			protected boolean m_selected;
			
			public ColorPane(Color c) {
				m_c = c;
				setBackground(c);
				setBorder(m_unselectedBorder);
				String msg = "R "+c.getRed()+", G "+c.getGreen()+", B "+c.getBlue();
				setToolTipText(msg);
				addMouseListener(this);
			}
			
			public Color getColor() { return m_c; }
			
			public Dimension getPreferredSize() {
				return new Dimension(15, 15);
			}
			
			public Dimension getMaximumSize() { return getPreferredSize(); }
			
			public Dimension getMinimumSize() { return getPreferredSize(); }
			
			public void setSelected(boolean selected) {
				m_selected = selected;
				if (m_selected) setBorder(m_selectedBorder);
				else setBorder(m_unselectedBorder);
			}
			
			public boolean isSelected() { return m_selected; }
			
			public void mousePressed(MouseEvent e) {}
			
			public void mouseClicked(MouseEvent e) {}
			
			public void mouseReleased(MouseEvent e) {
				setColor(m_c);
				MenuSelectionManager.defaultManager().clearSelectedPath();
				doSelection();
			}
			
			public void mouseEntered(MouseEvent e) {
				setBorder(m_activeBorder);
			}
			
			public void mouseExited(MouseEvent e) {
				setBorder(m_selected ? m_selectedBorder : m_unselectedBorder);
			}
		}
	}
	
	class Undoer implements UndoableEditListener {
		public Undoer() {
			m_undo.die();
			updateUndo();
		}
		
		public void undoableEditHappened(UndoableEditEvent e) {
			UndoableEdit edit = e.getEdit();
			m_undo.addEdit(e.getEdit());
			updateUndo();
		}
	}
	
	class ParagraphDialog extends JDialog {
		protected int m_option = JOptionPane.CLOSED_OPTION;
		protected MutableAttributeSet m_attributes;
		protected JTextField m_lineSpacing;
		protected JTextField m_spaceAbove;
		protected JTextField m_spaceBelow;
		protected JTextField m_firstIndent;
		protected JTextField m_leftIndent;
		protected JTextField m_rightIndent;
		protected SmallToggleButton m_btLeft;
		protected SmallToggleButton m_btCenter;
		protected SmallToggleButton m_btRight;
		protected SmallToggleButton m_btJustified;
		protected ParagraphPreview m_preview;
		
		public ParagraphDialog(JFrame parent) {
			super(parent, "Paragraph", true);
			getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
			JPanel p = new JPanel(new GridLayout(1, 2, 5, 2));
			JPanel ps = new JPanel(new GridLayout(3, 2, 10, 2));
			ps.setBorder(new TitledBorder(new EtchedBorder(), "Space"));
			ps.add(new JLabel("Line spacing:"));
			m_lineSpacing = new JTextField();
			ps.add(m_lineSpacing);
			ps.add(new JLabel("Space above:"));
			m_spaceAbove = new JTextField();
			ps.add(m_spaceAbove);
			ps.add(new JLabel("Space below:"));
			m_spaceBelow = new JTextField();
			ps.add(m_spaceBelow);
			p.add(ps);
			JPanel pi = new JPanel(new GridLayout(3, 2, 10, 2));
			pi.setBorder(new TitledBorder(new EtchedBorder(), "Indent"));
			pi.add(new JLabel("First indent:"));
			m_firstIndent = new JTextField();
			pi.add(m_firstIndent);
			pi.add(new JLabel("Left indent:"));
			m_leftIndent = new JTextField();
			pi.add(m_leftIndent);
			pi.add(new JLabel("Right indent:"));
			m_rightIndent = new JTextField();
			pi.add(m_rightIndent);
			p.add(pi);
			getContentPane().add(p);
			getContentPane().add(Box.createVerticalStrut(5));
			p = new JPanel();
			p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
			p.add(Box.createHorizontalStrut(10));
			p.add(new JLabel("Alignment:"));
			p.add(Box.createHorizontalStrut(20));
			
			ButtonGroup bg = new ButtonGroup();
			ImageIcon img = new ImageIcon("gauche.gif");
			m_btLeft = new SmallToggleButton(false, img, img, "Left");
			bg.add(m_btLeft);
			p.add(m_btLeft);
			img = new ImageIcon("centre.gif");
			m_btCenter = new SmallToggleButton(false, img, img, "Center");
			bg.add(m_btCenter);
			p.add(m_btCenter);
			img = new ImageIcon("droite.gif");
			m_btRight = new SmallToggleButton(false, img, img, "Right");
			bg.add(m_btRight);
			p.add(m_btRight);
			img = new ImageIcon("justifie.gif");
			m_btJustified = new SmallToggleButton(false, img, img, "Justify");
			bg.add(m_btJustified);
			p.add(m_btJustified);
			getContentPane().add(p);
			
			p = new JPanel(new BorderLayout());
			p.setBorder(new TitledBorder(new EtchedBorder(), "Preview"));
			m_preview = new ParagraphPreview();
			p.add(m_preview, BorderLayout.CENTER);
			getContentPane().add(p);
			
			p = new JPanel(new FlowLayout());
			JPanel p1 = new JPanel(new GridLayout(1, 2, 10, 2));
			JButton btOK = new JButton("OK");
			ActionListener lst = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					m_option = JOptionPane.OK_OPTION;
					setVisible(false);
				}
			};
			btOK.addActionListener(lst);
			p1.add(btOK);
			
			JButton btCancel = new JButton("Cancel");
			lst = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					m_option = JOptionPane.CANCEL_OPTION;
					setVisible(false);
				}
			};
			btCancel.addActionListener(lst);
			p1.add(btCancel);
			p.add(p1);
			getContentPane().add(p);
			
			pack();
			setResizable(false);
			
			FocusListener flst = new FocusListener() {
				public void focusGained(FocusEvent e) {}
				public void focusLost(FocusEvent e) { updatePreview(); }
			};
			m_lineSpacing.addFocusListener(flst);
			m_spaceAbove.addFocusListener(flst);
			m_spaceBelow.addFocusListener(flst);
			m_firstIndent.addFocusListener(flst);
			m_leftIndent.addFocusListener(flst);
			m_rightIndent.addFocusListener(flst);
			
			lst = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					updatePreview();
				}
			};
			m_btLeft.addActionListener(lst);
			m_btCenter.addActionListener(lst);
			m_btRight.addActionListener(lst);
			m_btJustified.addActionListener(lst);
		}
		
		public void setAttributes(AttributeSet a) {
			m_attributes = new SimpleAttributeSet(a);
			m_lineSpacing.setText(Float.toString(StyleConstants.getLineSpacing(a)));
			m_spaceAbove.setText(Float.toString(StyleConstants.getSpaceAbove(a)));
			m_spaceBelow.setText(Float.toString(StyleConstants.getSpaceBelow(a)));
			m_firstIndent.setText(Float.toString(StyleConstants.getFirstLineIndent(a)));
			m_leftIndent.setText(Float.toString(StyleConstants.getLeftIndent(a)));
			m_rightIndent.setText(Float.toString(StyleConstants.getRightIndent(a)));
			
			int alignment = StyleConstants.getAlignment(a);
			if (alignment == StyleConstants.ALIGN_LEFT)
				m_btLeft.setSelected(true);
			else if (alignment == StyleConstants.ALIGN_CENTER)
				m_btCenter.setSelected(true);
			else if (alignment == StyleConstants.ALIGN_RIGHT)
				m_btRight.setSelected(true);
			else if (alignment == StyleConstants.ALIGN_JUSTIFIED)
				m_btJustified.setSelected(true);
			
			updatePreview();
		}
		
		public AttributeSet getAttributes() {
			if (m_attributes == null) return null;
			float value;
			try {
				value = Float.parseFloat(m_lineSpacing.getText());
				StyleConstants.setLineSpacing(m_attributes, value);
			} catch (NumberFormatException ex) {}
			
			try {
				value = Float.parseFloat(m_spaceAbove.getText());
				StyleConstants.setSpaceAbove(m_attributes, value);
			} catch (NumberFormatException ex) {}
			
			try {
				value = Float.parseFloat(m_spaceBelow.getText());
				StyleConstants.setSpaceBelow(m_attributes, value);
			} catch (NumberFormatException ex) {}
			
			try {
				value = Float.parseFloat(m_firstIndent.getText());
				StyleConstants.setFirstLineIndent(m_attributes, value);
			} catch (NumberFormatException ex) {}
			
			try {
				value = Float.parseFloat(m_leftIndent.getText());
				StyleConstants.setLeftIndent(m_attributes, value);
			} catch (NumberFormatException ex) {}
			
			try {
				value = Float.parseFloat(m_rightIndent.getText());
				StyleConstants.setRightIndent(m_attributes, value);
			} catch (NumberFormatException ex) {}
			
			StyleConstants.setAlignment(m_attributes, getAlignment());
			return m_attributes;
		}
		
		public int getOption() {
			return m_option;
		}
		
		protected void updatePreview() {
			m_preview.repaint();
		}
		
		protected int getAlignment() {
			if (m_btLeft.isSelected())
				return StyleConstants.ALIGN_LEFT;
			if (m_btCenter.isSelected())
				return StyleConstants.ALIGN_CENTER;
			else if (m_btRight.isSelected())
				return StyleConstants.ALIGN_RIGHT;
			else
				return StyleConstants.ALIGN_JUSTIFIED;
		}
		
		class ParagraphPreview extends JPanel {
			protected Font m_fn = new Font("Monospace", Font.PLAIN, 6);
			protected String m_dummy = "abcdefghjklm";
			protected float  m_scaleX = 0.25f;
			protected float  m_scaleY = 0.25f;
			protected Random m_random = new Random();
			
			public ParagraphPreview() {
				setBackground(Color.white);
				setForeground(Color.black);
				setOpaque(true);
				setBorder(new LineBorder(Color.black));
				setPreferredSize(new Dimension(120, 56));
			}
			
			public void paintComponent(Graphics g) {
				super.paintComponent(g);
				float lineSpacing = 0;
				float spaceAbove  = 0;
				float spaceBelow  = 0;
				float firstIndent = 0;
				float leftIndent  = 0;
				float rightIndent = 0;
				
				try {
					lineSpacing = Float.parseFloat(m_lineSpacing.getText());
				} catch (NumberFormatException ex) {}
				
				try {
					spaceAbove = Float.parseFloat(m_spaceAbove.getText());
				} catch (NumberFormatException ex) {}
				
				try {
					spaceBelow = Float.parseFloat(m_spaceBelow.getText());
				} catch (NumberFormatException ex) {}
				
				try {
					firstIndent = Float.parseFloat(m_firstIndent.getText());
				} catch (NumberFormatException ex) {}
				
				try {
					leftIndent = Float.parseFloat(m_leftIndent.getText());
				} catch (NumberFormatException ex) {}
				
				try {
					rightIndent = Float.parseFloat(m_rightIndent.getText());
				} catch (NumberFormatException ex) {}
				
				m_random.setSeed(1959);    // Use same seed every time
				
				g.setFont(m_fn);
				FontMetrics fm = g.getFontMetrics();
				int h = fm.getAscent();
				int s  = Math.max((int)(lineSpacing*m_scaleY), 1);
				int s1 = Math.max((int)(spaceAbove*m_scaleY), 0) + s;
				int s2 = Math.max((int)(spaceBelow*m_scaleY), 0) + s;
				int y = 5+h;
				
				int xMarg = 20;
				int x0 = Math.max((int)(firstIndent*m_scaleX)+xMarg, 3);
				int x1 = Math.max((int)(leftIndent*m_scaleX)+xMarg, 3);
				int x2 = Math.max((int)(rightIndent*m_scaleX)+xMarg, 3);
				int xm0 = getWidth()-xMarg;
				int xm1 = getWidth()-x2;
				
				int n = (int)((getHeight()-(2*h+s1+s2-s+10))/(h+s));
				n = Math.max(n, 1);
				
				g.setColor(Color.lightGray);
				int x = xMarg;
				drawLine(g, x, y, xm0, xm0, fm, StyleConstants.ALIGN_LEFT);
				y += h+s1;
				
				g.setColor(Color.gray);
				int alignment = getAlignment();
				for (int k=0; k<n; k++) {
					x = (k==0 ? x0 : x1);
					int xLen = (k==n-1 ? xm1/2 : xm1);
					if (k==n-1 && alignment==StyleConstants.ALIGN_JUSTIFIED)
						alignment = StyleConstants.ALIGN_LEFT;
					drawLine(g, x, y, xm1, xLen, fm, alignment);
					y += h+s;
				}
				
				y += s2-s;
				x = xMarg;
				g.setColor(Color.lightGray);
				drawLine(g, x, y, xm0, xm0, fm, StyleConstants.ALIGN_LEFT);
			}
			
			protected void drawLine(Graphics g, int x, int y, int xMax, int xLen, FontMetrics fm, int alignment) {
				if (y > getHeight()-3) return;
				StringBuffer s = new StringBuffer();
				String str1;
				int xx = x;
				while (true) {
					int m = m_random.nextInt(10)+1;
					str1 = m_dummy.substring(0, m)+" ";
					int len = fm.stringWidth(str1);
					if (xx+len >= xLen) break;
					xx += len;
					s.append(str1);
				}
				String str = s.toString();
				
				switch (alignment) {
				case StyleConstants.ALIGN_LEFT:
					g.drawString(str, x, y);
				break;
				case StyleConstants.ALIGN_CENTER:
					xx = (xMax+x-fm.stringWidth(str))/2;
				g.drawString(str, xx, y);
				break;
				case StyleConstants.ALIGN_RIGHT:
					xx = xMax-fm.stringWidth(str);
				g.drawString(str, xx, y);
				break;
				case StyleConstants.ALIGN_JUSTIFIED:
					while (x+fm.stringWidth(str) < xMax)
						str += "a";
				g.drawString(str, x, y);
				break;
				}
			}
		}
	}
	
	public WordProcessor() {
		super("Carnet");
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {}
		
		setSize(640, 480);
		// Make sure we install the editor kit before creating
		// the initial document.
		m_monitor = new JTextPane();
		m_kit = new RTFEditorKit();
		m_monitor.setEditorKit(m_kit);
		m_context = new StyleContext();
		m_doc = new DefaultStyledDocument(m_context);
		m_monitor.setDocument(m_doc);
		conteneur = new JScrollPane(m_monitor);
		getContentPane().add(conteneur, BorderLayout.CENTER);
		menuBar = createMenuBar();
		setJMenuBar(menuBar);
		m_chooser = new JFileChooser();
		m_chooser.setCurrentDirectory(new File("."));
		m_rtfFilter = new SimpleFilter("rtf", "RTF Documents");
		m_chooser.setFileFilter(m_rtfFilter);
		
		m_gifFilter = new SimpleFilter("gif", "GIF images");
		m_jpgFilter = new SimpleFilter("jpg", "JPG images");
		CaretListener lst = new CaretListener() {
			public void caretUpdate(CaretEvent e) {
				showAttributes(e.getDot());
			}
		};
		m_monitor.addCaretListener(lst);
		
		FocusListener flst = new FocusListener() {
			public void focusGained(FocusEvent e) {
				if (m_xStart>=0 && m_xFinish>=0)
					if (m_monitor.getCaretPosition()==m_xStart) {
						m_monitor.setCaretPosition(m_xFinish);
						m_monitor.moveCaretPosition(m_xStart);
					}
					else
						m_monitor.select(m_xStart, m_xFinish);
			}
			
			public void focusLost(FocusEvent e) {
				m_xStart = m_monitor.getSelectionStart();
				m_xFinish = m_monitor.getSelectionEnd();
			}
		};
		m_monitor.addFocusListener(flst);
		
		WindowListener wndCloser = new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		};
		addWindowListener(wndCloser);
		
		showAttributes(0);
		showStyles();
		m_doc.addUndoableEditListener(new Undoer());
		setVisible(true);
	}
	
	class FontDialog extends JDialog {
		protected int m_option = JOptionPane.CLOSED_OPTION;
		protected OpenList m_lstFontName;
		protected OpenList m_lstFontSize;
		protected MutableAttributeSet m_attributes;
		protected JCheckBox m_chkBold;
		protected JCheckBox m_chkItalic;
		protected JCheckBox m_chkUnderline;
		protected JCheckBox m_chkStrikethrough;
		protected JCheckBox m_chkSubscript;
		protected JCheckBox m_chkSuperscript;
		protected JComboBox m_cbColor;
		protected JLabel m_preview;
		
		public FontDialog(JFrame parent, String[] names, String[] sizes) {
			super(parent, "Font", true);
			getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
			JPanel p = new JPanel(new GridLayout(1, 2, 10, 2));
			p.setBorder(new TitledBorder(new EtchedBorder(), "Font"));
			m_lstFontName = new OpenList(names, "Name:");
			p.add(m_lstFontName);
			
			m_lstFontSize = new OpenList(sizes, "Size:");
			p.add(m_lstFontSize);
			getContentPane().add(p);
			
			p = new JPanel(new GridLayout(2, 3, 10, 5));
			p.setBorder(new TitledBorder(new EtchedBorder(), "Effects"));
			m_chkBold = new JCheckBox("Bold");
			p.add(m_chkBold);
			m_chkItalic = new JCheckBox("Italic");
			p.add(m_chkItalic);
			m_chkUnderline = new JCheckBox("Underline");
			p.add(m_chkUnderline);
			m_chkStrikethrough = new JCheckBox("Strikeout");
			p.add(m_chkStrikethrough);
			m_chkSubscript = new JCheckBox("Subscript");
			p.add(m_chkSubscript);
			m_chkSuperscript = new JCheckBox("Superscript");
			p.add(m_chkSuperscript);
			getContentPane().add(p);
			
			getContentPane().add(Box.createVerticalStrut(5));
			p = new JPanel();
			p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
			p.add(Box.createHorizontalStrut(10));
			p.add(new JLabel("Color:"));
			p.add(Box.createHorizontalStrut(20));
			m_cbColor = new JComboBox();
			
			int[] values = new int[] { 0, 128, 192, 255 };
			for (int r=0; r<values.length; r++) {
				for (int g=0; g<values.length; g++) {
					for (int b=0; b<values.length; b++) {
						Color c = new Color(values[r], values[g], values[b]);
						m_cbColor.addItem(c);
					}
				}
			}
			
			m_cbColor.setRenderer(new ColorComboRenderer());
			p.add(m_cbColor);
			p.add(Box.createHorizontalStrut(10));
			getContentPane().add(p);
			
			p = new JPanel(new BorderLayout());
			p.setBorder(new TitledBorder(new EtchedBorder(), "Preview"));
			m_preview = new JLabel("Preview Font", JLabel.CENTER);
			m_preview.setBackground(Color.white);
			m_preview.setForeground(Color.black);
			m_preview.setOpaque(true);
			m_preview.setBorder(new LineBorder(Color.black));
			m_preview.setPreferredSize(new Dimension(120, 40));
			p.add(m_preview, BorderLayout.CENTER);
			getContentPane().add(p);
			
			p = new JPanel(new FlowLayout());
			JPanel p1 = new JPanel(new GridLayout(1, 2, 10, 2));
			JButton btOK = new JButton("OK");
			ActionListener lst = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					m_option = JOptionPane.OK_OPTION;
					setVisible(false);
				}
			};
			btOK.addActionListener(lst);
			p1.add(btOK);
			
			JButton btCancel = new JButton("Cancel");
			lst = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					m_option = JOptionPane.CANCEL_OPTION;
					setVisible(false);
				}
			};
			btCancel.addActionListener(lst);
			p1.add(btCancel);
			p.add(p1);
			getContentPane().add(p);
			
			pack();
			setResizable(false);
			Dimension d1 = getSize();
			Dimension d2 = parent.getSize();
			int x = Math.max((d2.width-d1.width)/2, 0);
			int y = Math.max((d2.height-d1.height)/2, 0);
			setBounds(x, y, d1.width, d1.height);
			
			ListSelectionListener lsel = new ListSelectionListener() {
				public void valueChanged(ListSelectionEvent e) {
					updatePreview();
				}
			};
			m_lstFontName.addListSelectionListener(lsel);
			m_lstFontSize.addListSelectionListener(lsel);
			
			lst = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					updatePreview();
				}
			};
			m_chkBold.addActionListener(lst);
			m_chkItalic.addActionListener(lst);
			m_cbColor.addActionListener(lst);
		}
		
		public void setAttributes(AttributeSet a) {
			m_attributes = new SimpleAttributeSet(a);
			String name = StyleConstants.getFontFamily(a);
			m_lstFontName.setSelected(name);
			int size = StyleConstants.getFontSize(a);
			m_lstFontSize.setSelectedInt(size);
			m_chkBold.setSelected(StyleConstants.isBold(a));
			m_chkItalic.setSelected(StyleConstants.isItalic(a));
			m_chkUnderline.setSelected(StyleConstants.isUnderline(a));
			m_chkStrikethrough.setSelected(StyleConstants.isStrikeThrough(a));
			m_chkSubscript.setSelected(StyleConstants.isSubscript(a));
			m_chkSuperscript.setSelected(StyleConstants.isSuperscript(a));
			m_cbColor.setSelectedItem(StyleConstants.getForeground(a));
			updatePreview();
		}
		
		public AttributeSet getAttributes() {
			if (m_attributes == null)
				return null;
			StyleConstants.setFontFamily(m_attributes, m_lstFontName.getSelected());
			StyleConstants.setFontSize(m_attributes, m_lstFontSize.getSelectedInt());
			StyleConstants.setBold(m_attributes, m_chkBold.isSelected());
			StyleConstants.setItalic(m_attributes, m_chkItalic.isSelected());
			StyleConstants.setUnderline(m_attributes, m_chkUnderline.isSelected());
			StyleConstants.setStrikeThrough(m_attributes, m_chkStrikethrough.isSelected());
			StyleConstants.setSubscript(m_attributes, m_chkSubscript.isSelected());
			StyleConstants.setSuperscript(m_attributes, m_chkSuperscript.isSelected());
			StyleConstants.setForeground(m_attributes, (Color)m_cbColor.getSelectedItem());
			return m_attributes;
		}
		
		public int getOption() { return m_option; }
		
		protected void updatePreview() {
			String name = m_lstFontName.getSelected();
			int size = m_lstFontSize.getSelectedInt();
			if (size <= 0)
				return;
			int style = Font.PLAIN;
			if (m_chkBold.isSelected())
				style |= Font.BOLD;
			if (m_chkItalic.isSelected())
				style |= Font.ITALIC;
			
			// Bug Alert! This doesn't work if only style is changed.
			Font fn = new Font(name, style, size);
			m_preview.setFont(fn);
			
			Color c = (Color)m_cbColor.getSelectedItem();
			m_preview.setForeground(c);
			m_preview.repaint();
		}
	}
	
	class OpenList extends JPanel implements ListSelectionListener, ActionListener {
		protected JLabel m_title;
		protected JTextField m_text;
		protected JList m_list;
		protected JScrollPane m_scroll;
		
		public OpenList(String[] data, String title) {
			setLayout(null);
			m_title = new JLabel(title, JLabel.LEFT);
			add(m_title);
			m_text = new JTextField();
			m_text.addActionListener(this);
			add(m_text);
			m_list = new JList(data);
			m_list.setVisibleRowCount(4);
			m_list.addListSelectionListener(this);
			m_scroll = new JScrollPane(m_list);
			add(m_scroll);
		}
		
		public void setSelected(String sel) {
			m_list.setSelectedValue(sel, true);
			m_text.setText(sel);
		}
		
		public String getSelected() { return m_text.getText(); }
		
		public void setSelectedInt(int value) {
			setSelected(Integer.toString(value));
		}
		
		public int getSelectedInt() {
			try {
				return Integer.parseInt(getSelected());
			}
			catch (NumberFormatException ex) { return -1; }
		}
		
		public void valueChanged(ListSelectionEvent e) {
			Object obj = m_list.getSelectedValue();
			if (obj != null)
				m_text.setText(obj.toString());
		}
		
		public void actionPerformed(ActionEvent e) {
			ListModel model = m_list.getModel();
			String key = m_text.getText().toLowerCase();
			for (int k=0; k<model.getSize(); k++) {
				String data = (String)model.getElementAt(k);
				if (data.toLowerCase().startsWith(key)) {
					m_list.setSelectedValue(data, true);
					break;
				}
			}
		}
		
		public void addListSelectionListener(ListSelectionListener lst) {
			m_list.addListSelectionListener(lst);
		}
		
		public Dimension getPreferredSize() {
			Insets ins = getInsets();
			Dimension d1 = m_title.getPreferredSize();
			Dimension d2 = m_text.getPreferredSize();
			Dimension d3 = m_scroll.getPreferredSize();
			int w = Math.max(Math.max(d1.width, d2.width), d3.width);
			int h = d1.height + d2.height + d3.height;
			return new Dimension(w+ins.left+ins.right,
					h+ins.top+ins.bottom);
		}
		
		public Dimension getMaximumSize() {
			Insets ins = getInsets();
			Dimension d1 = m_title.getMaximumSize();
			Dimension d2 = m_text.getMaximumSize();
			Dimension d3 = m_scroll.getMaximumSize();
			int w = Math.max(Math.max(d1.width, d2.width), d3.width);
			int h = d1.height + d2.height + d3.height;
			return new Dimension(w+ins.left+ins.right,
					h+ins.top+ins.bottom);
		}
		
		public Dimension getMinimumSize() {
			Insets ins = getInsets();
			Dimension d1 = m_title.getMinimumSize();
			Dimension d2 = m_text.getMinimumSize();
			Dimension d3 = m_scroll.getMinimumSize();
			int w = Math.max(Math.max(d1.width, d2.width), d3.width);
			int h = d1.height + d2.height + d3.height;
			return new Dimension(w+ins.left+ins.right,
					h+ins.top+ins.bottom);
		}
		
		public void doLayout() {
			Insets ins = getInsets();
			Dimension d = getSize();
			int x = ins.left;
			int y = ins.top;
			int w = d.width-ins.left-ins.right;
			int h = d.height-ins.top-ins.bottom;
			
			Dimension d1 = m_title.getPreferredSize();
			m_title.setBounds(x, y, w, d1.height);
			y += d1.height;
			Dimension d2 = m_text.getPreferredSize();
			m_text.setBounds(x, y, w, d2.height);
			y += d2.height;
			m_scroll.setBounds(x, y, w, h-y);
		}
	}
	
	class FindDialog extends JDialog {
	  protected WordProcessor m_owner;
	  protected JTabbedPane m_tb;
	  protected JTextField m_txtFind1;
	  protected JTextField m_txtFind2;
	  protected Document m_docFind;
	  protected Document m_docReplace;
	  protected ButtonModel m_modelWord;
	  protected ButtonModel m_modelCase;
	  protected ButtonModel m_modelUp;
	  protected ButtonModel m_modelDown;

	  protected int m_searchIndex = -1;
	  protected boolean m_searchUp = false;
	  protected String  m_searchData;

	  public FindDialog(WordProcessor owner, int index) {
	    super(owner, "Find and Replace", false);
	    m_owner = owner;
	    m_tb = new JTabbedPane();

	    // "Find" panel
	    JPanel p1 = new JPanel(new BorderLayout());
	    JPanel pc1 = new JPanel(new BorderLayout());

	    JPanel pf = new JPanel();
	    pf.setLayout(new DialogLayout(20, 5));
	    pf.setBorder(new EmptyBorder(8, 5, 8, 0));
	    pf.add(new JLabel("Find what:"));

	    m_txtFind1 = new JTextField();
	    m_docFind = m_txtFind1.getDocument();
	    pf.add(m_txtFind1);
	    pc1.add(pf, BorderLayout.CENTER);

	    JPanel po = new JPanel(new GridLayout(2, 2, 8, 2));
	    po.setBorder(new TitledBorder(new EtchedBorder(),"Options"));

	    JCheckBox chkWord = new JCheckBox("Whole words only");
	    chkWord.setMnemonic('w');
	    m_modelWord = chkWord.getModel();
	    po.add(chkWord);

	    ButtonGroup bg = new ButtonGroup();
	    JRadioButton rdUp = new JRadioButton("Search up");
	    rdUp.setMnemonic('u');
	    m_modelUp = rdUp.getModel();
	    bg.add(rdUp);
	    po.add(rdUp);

	    JCheckBox chkCase = new JCheckBox("Match case");
	    chkCase.setMnemonic('c');
	    m_modelCase = chkCase.getModel();
	    po.add(chkCase);

	    JRadioButton rdDown = new JRadioButton("Search down", true);
	    rdDown.setMnemonic('d');
	    m_modelDown = rdDown.getModel();
	    bg.add(rdDown);
	    po.add(rdDown);
	    pc1.add(po, BorderLayout.SOUTH);

	    p1.add(pc1, BorderLayout.CENTER);

	    JPanel p01 = new JPanel(new FlowLayout());
	    JPanel p = new JPanel(new GridLayout(2, 1, 2, 8));

	    ActionListener findAction = new ActionListener() {
	      public void actionPerformed(ActionEvent e) {
	        findNext(false, true);
	      }
	    };
	    JButton btFind = new JButton("Find Next");
	    btFind.addActionListener(findAction);
	    btFind.setMnemonic('f');
	    p.add(btFind);

	    ActionListener closeAction = new ActionListener() {
	      public void actionPerformed(ActionEvent e) {
	        setVisible(false);
	      }
	    };

	    JButton btClose = new JButton("Close");
	    btClose.addActionListener(closeAction);
	    btClose.setDefaultCapable(true);
	    p.add(btClose);
	    p01.add(p);
	    p1.add(p01, BorderLayout.EAST);

	    m_tb.addTab("Find", p1);

	    // "Replace" panel
	    JPanel p2 = new JPanel(new BorderLayout());
	    JPanel pc2 = new JPanel(new BorderLayout());
	    JPanel pc = new JPanel();
	    pc.setLayout(new DialogLayout(20, 5));
	    pc.setBorder(new EmptyBorder(8, 5, 8, 0));

	    pc.add(new JLabel("Find what:"));
	    m_txtFind2 = new JTextField();
	    m_txtFind2.setDocument(m_docFind);
	    pc.add(m_txtFind2);
	    pc.add(new JLabel("Replace:"));
	    JTextField txtReplace = new JTextField();
	    m_docReplace = txtReplace.getDocument();
	    pc.add(txtReplace);
	    pc2.add(pc, BorderLayout.CENTER);

	    po = new JPanel(new GridLayout(2, 2, 8, 2));
	    po.setBorder(new TitledBorder(new EtchedBorder(),"Options"));

	    chkWord = new JCheckBox("Whole words only");
	    chkWord.setMnemonic('w');
	    chkWord.setModel(m_modelWord);
	    po.add(chkWord);

	    bg = new ButtonGroup();
	    rdUp = new JRadioButton("Search up");
	    rdUp.setMnemonic('u');
	    rdUp.setModel(m_modelUp);
	    bg.add(rdUp);
	    po.add(rdUp);

	    chkCase = new JCheckBox("Match case");
	    chkCase.setMnemonic('c');
	    chkCase.setModel(m_modelCase);
	    po.add(chkCase);

	    rdDown = new JRadioButton("Search down", true);
	    rdDown.setMnemonic('d');
	    rdDown.setModel(m_modelDown);
	    bg.add(rdDown);
	    po.add(rdDown);
	    pc2.add(po, BorderLayout.SOUTH);

	    p2.add(pc2, BorderLayout.CENTER);

	    JPanel p02 = new JPanel(new FlowLayout());
	    p = new JPanel(new GridLayout(3, 1, 2, 8));

	    ActionListener replaceAction = new ActionListener() {
	      public void actionPerformed(ActionEvent e) {
	        findNext(true, true);
	      }
	    };
	    JButton btReplace = new JButton("Replace");
	    btReplace.addActionListener(replaceAction);
	    btReplace.setMnemonic('r');
	    p.add(btReplace);
	    ActionListener replaceAllAction = new ActionListener() {
	      public void actionPerformed(ActionEvent e) {
	        int counter = 0;
	        while (true) {
	          int result = findNext(true, false);
	          if (result < 0)    // error
	            return;
	          else if (result == 0)    // no more
	            break;
	          counter++;
	        }
	        JOptionPane.showMessageDialog(m_owner, counter+" replacement(s) have been done", "Info", JOptionPane.INFORMATION_MESSAGE);
	      }
	    };
	    JButton btReplaceAll = new JButton("Replace All");
	    btReplaceAll.addActionListener(replaceAllAction);
	    btReplaceAll.setMnemonic('a');
	    p.add(btReplaceAll);

	    btClose = new JButton("Close");
	    btClose.addActionListener(closeAction);
	    btClose.setDefaultCapable(true);
	    p.add(btClose);
	    p02.add(p);
	    p2.add(p02, BorderLayout.EAST);

	    // Make button columns the same size
	    p01.setPreferredSize(p02.getPreferredSize());
	    m_tb.addTab("Replace", p2);
	    m_tb.setSelectedIndex(index);
	    getContentPane().add(m_tb, BorderLayout.CENTER);
	    WindowListener flst = new WindowAdapter() {
	      public void windowActivated(WindowEvent e) {
	        m_searchIndex = -1;
	        if (m_tb.getSelectedIndex()==0)
	          m_txtFind1.grabFocus();
	        else
	          m_txtFind2.grabFocus();
	      }

	      public void windowDeactivated(WindowEvent e) {
	        m_searchData = null;
	      }
	    };
	    addWindowListener(flst);
	    pack();
	    setResizable(false);
	  }

	  public void setSelectedIndex(int index) {
	    m_tb.setSelectedIndex(index);
	    setVisible(true);
	    m_searchIndex = -1;
	  }

	  public int findNext(boolean doReplace, boolean showWarnings) {
	    JTextPane monitor = m_owner.getTextPane();
	    int pos = monitor.getCaretPosition();
	    if (m_modelUp.isSelected() != m_searchUp) {
	      m_searchUp = m_modelUp.isSelected();
	      m_searchIndex = -1;
	    }
	    if (m_searchIndex == -1) {
	      try {
	        Document doc = m_owner.getDocument();
	        if (m_searchUp)
	          m_searchData = doc.getText(0, pos);
	        else
	          m_searchData = doc.getText(pos, doc.getLength()-pos);
	        m_searchIndex = pos;
	      } catch (BadLocationException ex) {
	        warning(ex.toString());
	        return -1;
	      }
	    }

	    String key = "";
	    try { key = m_docFind.getText(0, m_docFind.getLength()); }
	    catch (BadLocationException ex) {}
	    if (key.length()==0) {
	      warning("Please enter the target to search");
	      return -1;
	    }
	    if (!m_modelCase.isSelected()) {
	      m_searchData = m_searchData.toLowerCase();
	      key = key.toLowerCase();
	    }
	    if (m_modelWord.isSelected()) {
	      for (int k=0; k</*Utils.*/WORD_SEPARATORS.length; k++) {
	        if (key.indexOf(/*Utils.*/WORD_SEPARATORS[k]) >= 0) {
	          warning("The text target contains an illegal "+"character \'"+/*Utils.*/WORD_SEPARATORS[k]+"\'");
	          return -1;
	        }
	      }
	    }
	 
	    String replacement = "";
	    if (doReplace) {
	      try {
	        replacement = m_docReplace.getText(0, m_docReplace.getLength());
	      } catch (BadLocationException ex) {}
	    }

	    int xStart = -1;
	    int xFinish = -1;
	    while (true) {
	      if (m_searchUp)
	        xStart = m_searchData.lastIndexOf(key, pos-1);
	      else
	        xStart = m_searchData.indexOf(key, pos-m_searchIndex);
	      if (xStart < 0) {
	        if (showWarnings)
	          warning("Text not found");
	        return 0;
	      }

	      xFinish = xStart+key.length();

	      if (m_modelWord.isSelected()) {
	        boolean s1 = xStart>0;
	        boolean b1 = s1 && !/*Utils.*/isSeparator(m_searchData.charAt(xStart-1));
	        boolean s2 = xFinish<m_searchData.length();
	        boolean b2 = s2 && !/*Utils.*/isSeparator(m_searchData.charAt(xFinish));
	                   
	        if (b1 || b2)    // Not a whole word
	        {
	          if (m_searchUp && s1)    // Can continue up
	          {
	            pos = xStart;
	            continue;
	          }
	          if (!m_searchUp && s2)    // Can continue down
	          {
	            pos = xFinish;
	            continue;
	          }
	          // Found, but not a whole word, and we cannot continue
	          if (showWarnings)
	            warning("Text not found");
	          return 0;
	        }
	      }
	      break;
	    }

	    if (!m_searchUp) {
	      xStart += m_searchIndex;
	      xFinish += m_searchIndex;
	    }
	    if (doReplace) {
	      m_owner.setSelection(xStart, xFinish, m_searchUp);
	      monitor.replaceSelection(replacement);
	      m_owner.setSelection(xStart, xStart+replacement.length(), m_searchUp);
	      m_searchIndex = -1;
	    }
	    else
	      m_owner.setSelection(xStart, xFinish, m_searchUp);
	    return 1;
	  }

	  protected void warning(String message) {
	    JOptionPane.showMessageDialog(m_owner, message, "Warning", JOptionPane.INFORMATION_MESSAGE);
	  }
	}

//	class Utils {
	  public static final char[] WORD_SEPARATORS = {' ', '\t', '\n', '\r', '\f', '.', ',', ':', '-', '(', ')', '[', ']', '{', '}', '<', '>', '/', '|', '\\', '\'', '\"'};

	  public static boolean isSeparator(char ch) {
	    for (int k=0; k<WORD_SEPARATORS.length; k++)
	      if (ch == WORD_SEPARATORS[k])
	        return true;
	    return false;
	  }
//	}
	
	class ColorComboRenderer extends JPanel implements ListCellRenderer {
		protected Color m_color = Color.black;
		protected Color m_focusColor = (Color) UIManager.get("List.selectionBackground");
		protected Color m_nonFocusColor = Color.white;
		
		public Component getListCellRendererComponent(JList list, Object obj, int row, boolean sel, boolean hasFocus) {
			if (hasFocus || sel)
				setBorder(new CompoundBorder(
						new MatteBorder(2, 10, 2, 10, m_focusColor),
						new LineBorder(Color.black)));
			else
				setBorder(new CompoundBorder(
						new MatteBorder(2, 10, 2, 10, m_nonFocusColor),
						new LineBorder(Color.black)));
			
			if (obj instanceof Color)
				m_color = (Color) obj;
			return this;
		}
		
		public void paintComponent(Graphics g) {
			setBackground(m_color);
			super.paintComponent(g);
		}
	}
	
	protected JMenuBar createMenuBar() {
		JMenuBar menuBar = new JMenuBar();
		JMenu mFile = new JMenu("Fichier");
		mFile.setMnemonic('f');
		ImageIcon iconNew = new ImageIcon("nouveau.gif");
		Action actionNew = new AbstractAction("Nouveau", iconNew) {
			public void actionPerformed(ActionEvent e) {
				m_doc = new DefaultStyledDocument(m_context);
				m_monitor.setDocument(m_doc);
				showAttributes(0);
				showStyles();
				m_doc.addUndoableEditListener(new Undoer());
			}
		};
		JMenuItem item = mFile.add(actionNew);
		item.setMnemonic('n');
		ImageIcon iconOpen = new ImageIcon("ouvrir.gif");
		Action actionOpen = new AbstractAction("Ouvrir ...", iconOpen) {
			public void actionPerformed(ActionEvent e) {
				WordProcessor.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
				Thread runner = new Thread() {
					public void run() {
						if (m_chooser.showOpenDialog(WordProcessor.this) !=	JFileChooser.APPROVE_OPTION)
							return;
						WordProcessor.this.repaint();
						File fChoosen = m_chooser.getSelectedFile();
						// Recall that text component read/write operations are
						// thread safe. Its ok to do this in a separate thread.
						try {
							InputStream in = new FileInputStream(fChoosen);
							m_doc = new DefaultStyledDocument(m_context);
							m_kit.read(in, m_doc, 0);
							m_monitor.setDocument(m_doc);
							in.close();
						} catch (Exception ex) {
							ex.printStackTrace();
						}
						WordProcessor.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
					}
				};
				showAttributes(0);
				showStyles();
				runner.start();
				m_doc.addUndoableEditListener(new Undoer());
			}
		};
		item = mFile.add(actionOpen);
		item.setMnemonic('o');
		ImageIcon iconSave = new ImageIcon("enregistrer.gif");
		Action actionSave = new AbstractAction("Enregistrer ...", iconSave) {
			public void actionPerformed(ActionEvent e) {
				WordProcessor.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
				Thread runner = new Thread() {
					public void run() {
						if (m_chooser.showSaveDialog(WordProcessor.this) !=	JFileChooser.APPROVE_OPTION)
							return;
						WordProcessor.this.repaint();
						File fChoosen = m_chooser.getSelectedFile();
						// Recall that text component read/write operations are
						// thread safe. Its ok to do this in a separate thread.
						try {
							OutputStream out = new FileOutputStream(fChoosen);
							m_kit.write(out, m_doc, 0, m_doc.getLength());
							out.close();
						} catch (Exception ex) {
							ex.printStackTrace();
						}
						// Make sure chooser is updated to reflect new file
						m_chooser.rescanCurrentDirectory();
						WordProcessor.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
					}
				};
				runner.start();
			}
		};
		item = mFile.add(actionSave);
		item.setMnemonic('s');
		mFile.addSeparator();
		Action actionExit = new AbstractAction("Quitter") {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		};
		item = mFile.add(actionExit);
		item.setMnemonic('x');
		menuBar.add(mFile);
		m_toolBar = new JToolBar();
		JButton bNew = new SmallButton(actionNew, "Nouveau document");
		m_toolBar.add(bNew);
		JButton bOpen = new SmallButton(actionOpen, "Ouvrir un document RTF");
		m_toolBar.add(bOpen);
		JButton bSave = new SmallButton(actionSave, "Enregistrer un document RTF");
		m_toolBar.add(bSave);
		JMenu mEdit = new JMenu("Edition");
		mEdit.setMnemonic('e');
		
		Action action = new AbstractAction("Copier", new ImageIcon("copier.gif")) {
			public void actionPerformed(ActionEvent e) {
				m_monitor.copy();
			}
		};
		item = mEdit.add(action); 
		item.setMnemonic('c');
		item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK));
		
		action = new AbstractAction("Couper", new ImageIcon("couper.gif")) {
			public void actionPerformed(ActionEvent e) {
				m_monitor.cut();
			}
			
		};
		item = mEdit.add(action); 
		item.setMnemonic('t');
		item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK));
		
		action = new AbstractAction("Coller", new ImageIcon("coller.gif")) {
			public void actionPerformed(ActionEvent e) {
				m_monitor.paste();
			}
		};
		item = mEdit.add(action); 
		item.setMnemonic('p');
		item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK));
		mEdit.addSeparator();
		
		m_undoAction = new AbstractAction("Annuler", new ImageIcon("annuler.gif")) {
			public void actionPerformed(ActionEvent e) {
				try {
					m_undo.undo();
				}
				catch (CannotUndoException ex) {
					System.err.println("Unable to undo: " + ex);
				}
				updateUndo();
			}
		};
		item = mEdit.add(m_undoAction); 
//		item.setMnemonic('u');
		item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_MASK));
		
		m_redoAction = new AbstractAction("Rétablir", new ImageIcon("retablir.gif")) {
			public void actionPerformed(ActionEvent e) {
				try {
					m_undo.redo();
				} catch (CannotRedoException ex) {
					System.err.println("Unable to redo: " + ex);
				}
				updateUndo();
			}
		};
		item =  mEdit.add(m_redoAction); 
//		item.setMnemonic('r');
		item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, KeyEvent.CTRL_MASK));
		
		menuBar.add(mEdit);
		GraphicsEnvironment ge = GraphicsEnvironment.
		getLocalGraphicsEnvironment();
		m_fontNames = ge.getAvailableFontFamilyNames();
		
		m_toolBar.addSeparator();
		m_cbFonts = new JComboBox(m_fontNames);
		m_cbFonts.setMaximumSize(m_cbFonts.getPreferredSize());
		m_cbFonts.setEditable(true);
		
		ActionListener lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				m_fontName = m_cbFonts.getSelectedItem().toString();
				MutableAttributeSet attr = new SimpleAttributeSet();
				StyleConstants.setFontFamily(attr, m_fontName);
				setAttributeSet(attr);
				m_monitor.grabFocus();
			}
		};
		m_cbFonts.addActionListener(lst);
		m_toolBar.add(m_cbFonts);
		
		m_toolBar.addSeparator();
		m_fontSizes = new String[] {"8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72"};
		m_cbSizes = new JComboBox(m_fontSizes);
		m_cbSizes.setMaximumSize(m_cbSizes.getPreferredSize());
		m_cbSizes.setEditable(true);
		m_fontDialog = new FontDialog(this, m_fontNames, m_fontSizes);
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int fontSize = 0;
				try {
					fontSize = Integer.parseInt(m_cbSizes.getSelectedItem().toString());
				} catch (NumberFormatException ex) { return; }
				
				m_fontSize = fontSize;
				MutableAttributeSet attr = new SimpleAttributeSet();
				StyleConstants.setFontSize(attr, fontSize);
				setAttributeSet(attr);
				m_monitor.grabFocus();
			}
		};
		m_cbSizes.addActionListener(lst);
		m_toolBar.add(m_cbSizes);
		
		m_toolBar.addSeparator();
		ImageIcon img1 = new ImageIcon("gras.gif");
		ImageIcon img2 = new ImageIcon("gras2.gif");
		m_bBold = new SmallToggleButton(false, img1, img2, "Gras");
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				MutableAttributeSet attr = new SimpleAttributeSet();
				StyleConstants.setBold(attr, m_bBold.isSelected());
				setAttributeSet(attr);
				m_monitor.grabFocus();
			}
		};
		m_bBold.addActionListener(lst);
		m_toolBar.add(m_bBold);
		
		img1 = new ImageIcon("italique.gif");
		img2 = new ImageIcon("italique2.gif");
		m_bItalic = new SmallToggleButton(false, img1, img2, "Italique");
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				MutableAttributeSet attr = new SimpleAttributeSet();
				StyleConstants.setItalic(attr, m_bItalic.isSelected());
				setAttributeSet(attr);
				m_monitor.grabFocus();
			}
		};
		m_bItalic.addActionListener(lst);
		m_toolBar.add(m_bItalic);
		
		JMenu mFormat = new JMenu("Format");
		mFormat.setMnemonic('o');
		item = new JMenuItem("Police ...");
		item.setMnemonic('o');
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				WordProcessor.this.repaint();
				AttributeSet a = m_doc.getCharacterElement(m_monitor.getCaretPosition()).getAttributes();
				m_fontDialog.setAttributes(a);
				
				Dimension d1 = m_fontDialog.getSize();
				Dimension d2 = WordProcessor.this.getSize();
				int x = Math.max((d2.width-d1.width)/2, 0);
				int y = Math.max((d2.height-d1.height)/2, 0);
				m_fontDialog.setBounds(x + WordProcessor.this.getX(), y + WordProcessor.this.getY(), d1.width, d1.height);
				
				m_fontDialog.setVisible(true);
				if (m_fontDialog.getOption()==JOptionPane.OK_OPTION) {
					setAttributeSet(m_fontDialog.getAttributes());
					showAttributes(m_monitor.getCaretPosition());
				}
			}
		};
		item.addActionListener(lst);
		mFormat.add(item);
		
		mFormat.addSeparator();
		JMenu mStyle = new JMenu("Style");
		mStyle.setMnemonic('s');
		mFormat.add(mStyle);
		
		item = new JMenuItem("Mise à jour");
		item.setMnemonic('m');
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String name = (String)m_cbStyles.getSelectedItem();
				Style style = m_doc.getStyle(name);
				int p = m_monitor.getCaretPosition();
				AttributeSet a = m_doc.getCharacterElement(p).getAttributes();
				style.addAttributes(a);
				m_monitor.repaint();
			}
		};
		item.addActionListener(lst);
		mStyle.add(item);
		
		item = new JMenuItem("Rétablir");
		item.setMnemonic('r');
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String name = (String)m_cbStyles.getSelectedItem();
				Style style = m_doc.getStyle(name);
				setAttributeSet(style);
			}
		};
		item.addActionListener(lst);
		mStyle.add(item);
		item = new JMenuItem("Paragraphe ...");
		item.setMnemonic('p');
		m_paragraphDialog = new ParagraphDialog(this);
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				WordProcessor.this.repaint();
				AttributeSet a = m_doc.getCharacterElement(m_monitor.getCaretPosition()).getAttributes();
				m_paragraphDialog.setAttributes(a);
				
				Dimension d1 = m_paragraphDialog.getSize();
				Dimension d2 = WordProcessor.this.getSize();
				int x = Math.max((d2.width-d1.width)/2, 0);
				int y = Math.max((d2.height-d1.height)/2, 0);
				m_paragraphDialog.setBounds(x + WordProcessor.this.getX(),y + WordProcessor.this.getY(), d1.width, d1.height);
				
				m_paragraphDialog.setVisible(true);
				if (m_paragraphDialog.getOption()==JOptionPane.OK_OPTION) {
					setAttributeSet(/*TODO dlg*/m_paragraphDialog.getAttributes(), true);
					showAttributes(m_monitor.getCaretPosition());
				}
			}
		};
		item.addActionListener(lst);
		mFormat.add(item);
		mFormat.addSeparator();
		m_foreground = new ColorMenu("Foreground");
		m_foreground.setColor(m_monitor.getForeground());
		m_foreground.setMnemonic('f');
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				MutableAttributeSet attr = new SimpleAttributeSet();
				StyleConstants.setForeground(attr, m_foreground.getColor());
				setAttributeSet(attr);
			}
		};
		m_foreground.addActionListener(lst);
		mFormat.add(m_foreground);
		
		MenuListener ml = new MenuListener() {
			public void menuSelected(MenuEvent e) {
				int p = m_monitor.getCaretPosition();
				AttributeSet a = m_doc.getCharacterElement(p).getAttributes();
				Color c = StyleConstants.getForeground(a);
				m_foreground.setColor(c);
			}
			
			public void menuDeselected(MenuEvent e) {}
			
			public void menuCanceled(MenuEvent e) {}
		};
		m_foreground.addMenuListener(ml);
		
		// Bug Alert! JEditorPane background color
		// doesn't work as of Java 2 FCS.
		m_background = new ColorMenu("Background");
		m_background.setColor(m_monitor.getBackground());
		m_background.setMnemonic('b');
		lst = new ActionListener()  {
			public void actionPerformed(ActionEvent e) {
				MutableAttributeSet attr = new SimpleAttributeSet();
				StyleConstants.setBackground(attr, m_background.getColor());
				setAttributeSet(attr);
			}
		};
		m_background.addActionListener(lst);
		mFormat.add(m_background);
		
		ml = new MenuListener() {
			public void menuSelected(MenuEvent e) {
				int p = m_monitor.getCaretPosition();
				AttributeSet a = m_doc.getCharacterElement(p).getAttributes();
				Color c = StyleConstants.getBackground(a);
				m_background.setColor(c);
			}
			
			public void menuDeselected(MenuEvent e) {}
			public void menuCanceled(MenuEvent e) {}
		};
		m_background.addMenuListener(ml);
		// Bug Alert! Images do not get saved.
		mFormat.addSeparator();
		item = new JMenuItem("Inserer une  image"); 
		item.setMnemonic('i');
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				m_chooser.addChoosableFileFilter(m_gifFilter);
				m_chooser.addChoosableFileFilter(m_jpgFilter);
				m_chooser.setFileFilter(m_gifFilter);
				m_chooser.removeChoosableFileFilter(m_rtfFilter);
				Thread runner = new Thread() {
					public void run() {
						if (m_chooser.showOpenDialog(WordProcessor.this) != JFileChooser.APPROVE_OPTION)
							return;
						WordProcessor.this.repaint();
						File fChoosen = m_chooser.getSelectedFile();
						ImageIcon icon = new ImageIcon(fChoosen.getPath());
						int w = icon.getIconWidth();
						int h = icon.getIconHeight();
						if (w<=0 || h<=0) {
							JOptionPane.showMessageDialog(WordProcessor.this, "Error reading image file\n"+fChoosen.getPath(), "Warning", JOptionPane.WARNING_MESSAGE);
							return;
						}
						MutableAttributeSet attr = new SimpleAttributeSet();
						StyleConstants.setIcon(attr, icon);
						int p = m_monitor.getCaretPosition();
						try {
							m_doc.insertString(p, " ", attr);
						} catch (BadLocationException ex) {}
						
						// Its ok to do this outside of the event-dispatching
						// thread because the chooser is not visible here.
						m_chooser.addChoosableFileFilter(m_rtfFilter);
						m_chooser.setFileFilter(m_rtfFilter);
						m_chooser.removeChoosableFileFilter(m_gifFilter);
						m_chooser.removeChoosableFileFilter(m_jpgFilter);
					}
				};
				runner.start();
			}
		};
		item.addActionListener(lst);
		mFormat.add(item);
		
		menuBar.add(mFormat);
		
		m_toolBar.addSeparator();
		m_cbStyles = new JComboBox();
		m_cbStyles.setMaximumSize(new Dimension(100,20));//m_cbStyles.getPreferredSize());
		m_cbStyles.setEditable(true);
		m_toolBar.add(m_cbStyles);
		
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (m_skipUpdate || m_cbStyles.getItemCount()==0)
					return;
				String name = (String)m_cbStyles.getSelectedItem();
				int index = m_cbStyles.getSelectedIndex();
				int p = m_monitor.getCaretPosition();
				
				// New name entered
				if (index == -1) {
					m_cbStyles.addItem(name);
					Style style = m_doc.addStyle(name, null);
					AttributeSet a = m_doc.getCharacterElement(p).getAttributes();
					style.addAttributes(a);
					return;
				}
				
				// Apply the selected style
				Style currStyle = m_doc.getLogicalStyle(p);
				if (!currStyle.getName().equals(name)) {
					Style style = m_doc.getStyle(name);
					setAttributeSet(style);
				}
			}
		};
		m_cbStyles.addActionListener(lst);
		getContentPane().add(m_toolBar, BorderLayout.NORTH); 
		return menuBar;
	}
	
	protected void showAttributes(int p) {
		m_skipUpdate = true;
		AttributeSet a = m_doc.getCharacterElement(p).
		getAttributes();
		String name = StyleConstants.getFontFamily(a);
		if (!m_fontName.equals(name)) {
			m_fontName = name;
			m_cbFonts.setSelectedItem(name);
		}
		int size = StyleConstants.getFontSize(a);
		if (m_fontSize != size) {
			m_fontSize = size;
			m_cbSizes.setSelectedItem(Integer.toString(m_fontSize));
		}
		boolean bold = StyleConstants.isBold(a);
		if (bold != m_bBold.isSelected())
			m_bBold.setSelected(bold);
		boolean italic = StyleConstants.isItalic(a);
		if (italic != m_bItalic.isSelected())
			m_bItalic.setSelected(italic);
		Style style = m_doc.getLogicalStyle(p);
		name = style.getName();
		m_cbStyles.setSelectedItem(name);
		m_skipUpdate = false;
	}
	
	protected void setAttributeSet(AttributeSet attr) {
		setAttributeSet(attr, false);
	}
	
	protected void setAttributeSet(AttributeSet attr, boolean setParagraphAttributes) {
		if (m_skipUpdate)
			return;
		int xStart = m_monitor.getSelectionStart();
		int xFinish = m_monitor.getSelectionEnd();
		if (!m_monitor.hasFocus()) {
			xStart = m_xStart;
			xFinish = m_xFinish;
		}
		if (setParagraphAttributes)
			m_doc.setParagraphAttributes(xStart,xFinish - xStart, attr, false);
		else if (xStart != xFinish)
			m_doc.setCharacterAttributes(xStart,xFinish - xStart, attr, false);
		else {
			MutableAttributeSet inputAttributes = m_kit.getInputAttributes();
			inputAttributes.addAttributes(attr);
		}
	}
	
	protected void showStyles() {
		m_skipUpdate = true;
		if (m_cbStyles.getItemCount() > 0)
			m_cbStyles.removeAllItems();
		Enumeration en = m_doc.getStyleNames();
		while (en.hasMoreElements()) {
			String str = en.nextElement().toString();
			m_cbStyles.addItem(str);
		}
		m_skipUpdate = false;
	}
	
	protected void updateUndo() {
		if(m_undo.canUndo()) {
			m_undoAction.setEnabled(true);
			m_undoAction.putValue(Action.NAME, m_undo.getUndoPresentationName().replace("Défaire", "Annuler"));
		} else {
			m_undoAction.setEnabled(false);
			m_undoAction.putValue(Action.NAME, "Annuler");
		}
		if(m_undo.canRedo()) {
			m_redoAction.setEnabled(true);
			m_redoAction.putValue(Action.NAME, m_undo.getRedoPresentationName().replace("Refaire", "Rétablir"));
		} else {
			m_redoAction.setEnabled(false);
			m_redoAction.putValue(Action.NAME, "Rétablir");
		}
	}
	
	public Document getDocument() { return m_doc; }
	
	public JTextPane getTextPane() { return m_monitor; }
	
	public void setSelection(int xStart, int xFinish, boolean moveUp) {
		if (moveUp) {
			m_monitor.setCaretPosition(xFinish);
			m_monitor.moveCaretPosition(xStart);
		}
		else
			m_monitor.select(xStart, xFinish);
		m_xStart = m_monitor.getSelectionStart();
		m_xFinish = m_monitor.getSelectionEnd();
	}
	
	public static void main(String argv[]) {
		new WordProcessor();
	}
}
