/* * @(#)TeddyView.java * * Copyright (c) 2006 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.samples.teddy; import java.awt.event.*; import org.jhotdraw.app.*; import org.jhotdraw.samples.teddy.text.*; import org.jhotdraw.samples.teddy.regex.*; import org.jhotdraw.undo.*; import org.jhotdraw.samples.teddy.io.*; import java.lang.reflect.*; import java.awt.*; import java.beans.*; import java.util.prefs.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.undo.*; import java.io.*; import java.net.URI; import org.jhotdraw.app.action.edit.RedoAction; import org.jhotdraw.app.action.edit.UndoAction; import org.jhotdraw.gui.JFileURIChooser; import org.jhotdraw.gui.URIChooser; import org.jhotdraw.util.prefs.PreferencesUtil; /** * Provides a view on a text document. * <p> * See {@link org.jhotdraw.app.View} interface on how this view interacts with an application. * * @author Werner Randelshofer * @version $Id$ */ public class TeddyView extends AbstractView { private static final long serialVersionUID = 1L; private static Preferences prefs = PreferencesUtil.userNodeForPackage(TeddyView.class); protected JTextPane editor; private static class EditorPanel extends JPanel implements Scrollable { private static final long serialVersionUID = 1L; private JTextComponent editor; private boolean isLineWrap; public void setEditor(JTextComponent newValue) { editor = newValue; removeAll(); setLayout(new BorderLayout()); add(editor); setBackground(UIManager.getColor("TextField.background")); setOpaque(true); } public void setLineWrap(boolean newValue) { isLineWrap = newValue; editor.revalidate(); editor.repaint(); } public boolean getLineWrap() { return isLineWrap; } @Override public Dimension getPreferredScrollableViewportSize() { // System.out.println("EditorViewport: "+editor.getPreferredScrollableViewportSize()); return editor.getPreferredScrollableViewportSize(); } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return editor.getScrollableUnitIncrement(visibleRect, orientation, direction); } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return editor.getScrollableBlockIncrement(visibleRect, orientation, direction); } @Override public boolean getScrollableTracksViewportWidth() { return isLineWrap; } @Override public boolean getScrollableTracksViewportHeight() { return editor.getScrollableTracksViewportHeight(); } } protected EditorPanel editorViewport; /** * The undo/redo manager. */ protected UndoRedoManager undoManager; /** * The panel used for the find feature. */ private FindDialog findDialog; /** * The Matcher used to perform find operation. */ private Matcher matcher; /** Creates a new instance. */ public TeddyView() { prefs = PreferencesUtil.userNodeForPackage(TeddyView.class); initComponents(); // Init preferences statusBar.setVisible(prefs.getBoolean("statusBarVisible", false)); editor = createEditor(); editorViewport = new EditorPanel(); editorViewport.setEditor(editor); editorViewport.setLineWrap(prefs.getBoolean("lineWrap", true)); scrollPane.setViewportView(editorViewport); editor.addCaretListener(new CaretListener() { @Override public void caretUpdate(CaretEvent evt) { TeddyView.this.caretUpdate(evt); } }); scrollPane.getViewport().setBackground(editor.getBackground()); scrollPane.getViewport().addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent evt) { editor.requestFocus(); } }); Font font = getFont(); MutableAttributeSet attrs = ((StyledEditorKit) editor.getEditorKit()).getInputAttributes(); StyleConstants.setFontFamily(attrs, font.getFamily()); StyleConstants.setFontSize(attrs, font.getSize()); StyleConstants.setItalic(attrs, (font.getStyle() & Font.ITALIC) != 0); StyleConstants.setBold(attrs, (font.getStyle() & Font.BOLD) != 0); NumberedEditorKit editorKit = new NumberedEditorKit(); ((NumberedViewFactory) editorKit.getViewFactory()).setLineNumbersVisible(prefs.getBoolean("lineNumbersVisible", false)); editor.setEditorKit(editorKit); editor.setDocument(createDocument()); setPreferredSize(new Dimension(400, 400)); undoManager = new UndoRedoManager(); editor.getDocument().addUndoableEditListener(undoManager); undoManager.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { setHasUnsavedChanges(undoManager.hasSignificantEdits()); } }); } protected JTextPane createEditor() { return new JTextPane(); } @Override public void init() { initActions(); } @Override public void setEnabled(boolean newValue) { super.setEnabled(newValue); editor.setEnabled(newValue); scrollPane.setEnabled(newValue); } public void setStatusBarVisible(boolean newValue) { boolean oldValue = statusBar.isVisible(); statusBar.setVisible(newValue); prefs.putBoolean("statusBarVisible", newValue); firePropertyChange("statusBarVisible", oldValue, newValue); } public boolean isStatusBarVisible() { return statusBar.isVisible(); } public void setLineWrap(boolean newValue) { boolean oldValue = editorViewport.getLineWrap(); editorViewport.setLineWrap(newValue); prefs.putBoolean("lineWrap", newValue); firePropertyChange("lineWrap", oldValue, newValue); } public boolean isLineWrap() { return editorViewport.getLineWrap(); } private void initActions() { getActionMap().put(UndoAction.ID, undoManager.getUndoAction()); getActionMap().put(RedoAction.ID, undoManager.getRedoAction()); } @Override public void read(URI f, URIChooser chooser) throws IOException { String characterSet; if (chooser == null// || !(chooser instanceof JFileURIChooser) // || !(((JFileURIChooser) chooser).getAccessory() instanceof CharacterSetAccessory)// ) { characterSet = prefs.get("characterSet", "UTF-8"); } else { characterSet = ((CharacterSetAccessory) ((JFileURIChooser) chooser).getAccessory()).getCharacterSet(); } read(f, characterSet); } public void read(URI f, String characterSet) throws IOException { final Document doc = readDocument(new File(f), characterSet); try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { editor.getDocument().removeUndoableEditListener(undoManager); editor.setDocument(doc); doc.addUndoableEditListener(undoManager); undoManager.discardAllEdits(); } }); } catch (InterruptedException e) { // ignore } catch (InvocationTargetException e) { InternalError error = new InternalError(e.getMessage()); error.initCause(e); throw error; } } @Override public void write(URI f, URIChooser chooser) throws IOException { String characterSet, lineSeparator; if (chooser == null// || !(chooser instanceof JFileURIChooser) // || !(((JFileURIChooser) chooser).getAccessory() instanceof CharacterSetAccessory)// ) { characterSet = prefs.get("characterSet", "UTF-8"); lineSeparator = prefs.get("lineSeparator", "\n"); } else { characterSet = ((CharacterSetAccessory) ((JFileURIChooser) chooser).getAccessory()).getCharacterSet(); lineSeparator = ((CharacterSetAccessory) ((JFileURIChooser) chooser).getAccessory()).getLineSeparator(); } write(f, characterSet, lineSeparator); } public void write(URI f, String characterSet, String lineSeparator) throws IOException { writeDocument(editor.getDocument(), new File(f), characterSet, lineSeparator); try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { undoManager.setHasSignificantEdits(false); } }); } catch (InterruptedException e) { // ignore } catch (InvocationTargetException e) { InternalError error = new InternalError(e.getMessage()); error.initCause(e); throw error; } } /** * Reads a document from a file using the specified character set. */ private Document readDocument(File f, String characterSet) throws IOException { ProgressMonitorInputStream pin = new ProgressMonitorInputStream(this, "Reading " + f.getName(), new FileInputStream(f)); BufferedReader in = new BufferedReader(new InputStreamReader(pin, characterSet)); try { // PlainDocument doc = new PlainDocument(); StyledDocument doc = createDocument(); MutableAttributeSet attrs = ((StyledEditorKit) editor.getEditorKit()).getInputAttributes(); String line; boolean isFirst = true; while ((line = in.readLine()) != null) { if (isFirst) { isFirst = false; } else { doc.insertString(doc.getLength(), "\n", attrs); } doc.insertString(doc.getLength(), line, attrs); } return doc; } catch (BadLocationException e) { throw new IOException(e.getMessage()); } catch (OutOfMemoryError e) { System.err.println("out of memory!"); throw new IOException("Out of memory."); } finally { in.close(); } } @Override public void clear() { final Document newDocument = createDocument(); try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { editor.getDocument().removeUndoableEditListener(undoManager); editor.setDocument(newDocument); newDocument.addUndoableEditListener(undoManager); undoManager.discardAllEdits(); } }); } catch (InvocationTargetException ex) { ex.printStackTrace(); } catch (InterruptedException ex) { ex.printStackTrace(); } } protected StyledDocument createDocument() { DefaultStyledDocument doc = new DefaultStyledDocument(); doc.setParagraphAttributes(0, 1, ((StyledEditorKit) editor.getEditorKit()).getInputAttributes(), true); return doc; } /** * Writes a document into a file using the specified character set. */ private void writeDocument(Document doc, File f, String characterSet, String lineSeparator) throws IOException { LFWriter out = new LFWriter(new OutputStreamWriter(new FileOutputStream(f), characterSet)); out.setLineSeparator(lineSeparator); try { String sequence; for (int i = 0; i < doc.getLength(); i += 256) { out.write(doc.getText(i, Math.min(256, doc.getLength() - i))); } } catch (BadLocationException e) { throw new IOException(e.getMessage()); } finally { out.close(); undoManager.discardAllEdits(); } } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { statusBar = new javax.swing.JPanel(); caretInfoLabel = new javax.swing.JLabel(); scrollPane = new javax.swing.JScrollPane(); setLayout(new java.awt.BorderLayout()); statusBar.setLayout(new java.awt.BorderLayout()); caretInfoLabel.setFont(new java.awt.Font("Lucida Grande", 0, 11)); caretInfoLabel.setText("1:1"); caretInfoLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 3, 0, 3)); statusBar.add(caretInfoLabel, java.awt.BorderLayout.CENTER); add(statusBar, java.awt.BorderLayout.SOUTH); scrollPane.setBorder(null); scrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); add(scrollPane, java.awt.BorderLayout.CENTER); }// </editor-fold>//GEN-END:initComponents public Document getDocument() { return editor.getDocument(); } /** * Accessor for text area. * This is used by Actions that need ot act on the text area of the View. */ public void select(int start, int end) { editor.select(start, end); try { editor.scrollRectToVisible(editor.modelToView(start)); } catch (BadLocationException e) { e.printStackTrace(); } } /** * Accessor for text area. * This is used by Actions that need ot act on the text area of the View. */ public int getSelectionStart() { return editor.getSelectionStart(); } /** * Accessor for text area. * This is used by Actions that need ot act on the project text area. */ public int getSelectionEnd() { return editor.getSelectionEnd(); } /** * Determines the number of lines contained in the area. * * @return the number of lines > 0 */ public int getLineCount() { Element map = getDocument().getDefaultRootElement(); return map.getElementCount(); } /** * Accessor for text area. * This is used by Actions that need to act on the text area of the View. */ public void replaceRange(String str, int start, int end) { //editor.replaceRange(str, start, end); if (end < start) { throw new IllegalArgumentException("end before start"); } Document doc = getDocument(); if (doc != null) { try { if (doc instanceof AbstractDocument) { ((AbstractDocument) doc).replace(start, end - start, str, null); } else { doc.remove(start, end - start); doc.insertString(start, str, null); } } catch (BadLocationException e) { throw new IllegalArgumentException(e.getMessage()); } } } /** * Accessor for text area. * This is used by Actions that need ot act on the text area of the View. */ public int getLineOfOffset(int offset) throws BadLocationException { //return editor.getLineOfOffset(offset); Document doc = getDocument(); if (offset < 0) { throw new BadLocationException("Can't translate offset to line", -1); } else if (offset > doc.getLength()) { throw new BadLocationException("Can't translate offset to line", doc.getLength() + 1); } else { Element map = getDocument().getDefaultRootElement(); return map.getElementIndex(offset); } } /** * Accessor for text area. * This is used by Actions that need ot act on the text area of the View. */ public int getLineStartOffset(int line) throws BadLocationException { //return editor.getLineStartOffset(line); int lineCount = getLineCount(); if (line < 0) { throw new BadLocationException("Negative line", -1); } else if (line >= lineCount) { throw new BadLocationException("No such line", getDocument().getLength() + 1); } else { Element map = getDocument().getDefaultRootElement(); Element lineElem = map.getElement(line); return lineElem.getStartOffset(); } } public void fireEdit(UndoableEdit edit) { undoManager.addEdit(edit); } private void caretUpdate(javax.swing.event.CaretEvent evt) { try { int pos = editor.getCaretPosition(); int line = getLineOfOffset(pos); int lineStartOffset = getLineStartOffset(line); caretInfoLabel.setText((line + 1) + ":" + (pos - lineStartOffset + 1)); } catch (BadLocationException e) { caretInfoLabel.setText(e.toString()); } } public void setLineNumbersVisible(boolean newValue) { NumberedViewFactory viewFactory = (NumberedViewFactory) editor.getEditorKit(). getViewFactory(); boolean oldValue = viewFactory.isLineNumbersVisible(); if (oldValue != newValue) { viewFactory.setLineNumbersVisible(newValue); prefs.putBoolean("lineNumbersVisible", newValue); firePropertyChange("lineNumbersVisible", oldValue, newValue); editor.revalidate(); editor.repaint(); } } public boolean isLineNumbersVisible() { NumberedViewFactory viewFactory = (NumberedViewFactory) editor.getEditorKit(). getViewFactory(); return viewFactory.isLineNumbersVisible(); } // Variables declaration - do not modify//GEN-BEGIN:variables public javax.swing.JLabel caretInfoLabel; public javax.swing.JScrollPane scrollPane; public javax.swing.JPanel statusBar; // End of variables declaration//GEN-END:variables }