/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: TextWindow.java * * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool.user.ui; import com.sun.electric.Main; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.text.WeakReferences; import com.sun.electric.database.variable.CodeExpression; import com.sun.electric.database.variable.TextDescriptor; import com.sun.electric.database.variable.VarContext; import com.sun.electric.tool.Job; import com.sun.electric.tool.JobException; import com.sun.electric.tool.io.FileType; import com.sun.electric.tool.user.Highlighter; import com.sun.electric.tool.user.User; import com.sun.electric.tool.user.dialogs.OpenFile; import com.sun.electric.util.TextUtils; import java.awt.BorderLayout; import java.awt.Cursor; import java.awt.Font; import java.awt.Graphics; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.beans.PropertyChangeListener; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.tree.MutableTreeNode; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; /** * This class defines a text window for displaying text cells. */ public class TextWindow implements WindowContent { /** the cell that is in the window */ private Cell cell; /** the window frame containing this editwindow */ private WindowFrame wf; /** the overall panel with disp area and sliders */ private TextWindowPanel overall; /** true if text in the window is closing. */ private boolean finishing; /** true if text in the window is being reloaded. */ private boolean reloading; private JTextArea textArea; private JScrollPane scrollPane; private UndoManager undo = new UndoManager(); /** * Factory method to create a new TextWindow with a given cell, in a given WindowFrame. * @param cell the cell in this TextWindow. * @param wf the WindowFrame that this TextWindow lives in. */ public TextWindow(Cell cell, WindowFrame wf) { this.wf = wf; finishing = false; reloading = false; textArea = new JTextArea(); scrollPane = new JScrollPane(textArea); overall = new TextWindowPanel(); overall.setLayout(new BorderLayout()); overall.add(scrollPane, BorderLayout.CENTER); setCell(cell, VarContext.globalContext, null); TextWindowDocumentListener twDocumentListener = new TextWindowDocumentListener(this); textArea.getDocument().addDocumentListener(twDocumentListener); textArea.getDocument().addUndoableEditListener(new MyUndoableEditListener()); textArea.addFocusListener(twDocumentListener); } /** * This dummy class duplicates JPanel but makes it recognizable as * the JPanel in a TextWindow. */ public static class TextWindowPanel extends JPanel { } public void setCursor(Cursor cursor) { // nothing implemented in TextWindow } private void setCellFont(Cell cell) { String fontName = User.getDefaultTextCellFont(); int fontSize = User.getDefaultTextCellSize(); if (cell != null) { fontName = cell.getVarValue(Cell.TEXT_CELL_FONT_NAME, String.class, fontName); fontSize = cell.getVarValue(Cell.TEXT_CELL_FONT_SIZE, Integer.class, new Integer(fontSize)).intValue(); } textArea.setFont(new Font(fontName, 0, fontSize)); } private class MyUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { // Remember the edit and update the menus undo.addEdit(e.getEdit()); updateUndoRedo(); } } private static WeakReferences<PropertyChangeListener> undoListeners = new WeakReferences<PropertyChangeListener>(); private static WeakReferences<PropertyChangeListener> redoListeners = new WeakReferences<PropertyChangeListener>(); public static void addTextUndoListener(PropertyChangeListener l) { undoListeners.add(l); } public static void addTextRedoListener(PropertyChangeListener l) { redoListeners.add(l); } public static void removeTextUndoListener(PropertyChangeListener l) { undoListeners.remove(l); } public static void removeTextRedoListener(PropertyChangeListener l) { redoListeners.remove(l); } private void updateUndoRedo() { Job.getExtendedUserInterface().showUndoRedoStatus(undo.canUndo(), undo.canRedo()); } /** * Method to undo changes to text in this TextWindow. */ public void undo() { try { undo.undo(); updateUndoRedo(); } catch (CannotUndoException e) { System.out.println("Cannot undo"); } } /** * Method to redo changes to text in this TextWindow. */ public void redo() { try { undo.redo(); updateUndoRedo(); } catch (CannotRedoException e) { System.out.println("Cannot redo"); } } /** * Method to repaint this TextWindow. */ public void paint(Graphics g) { // to enable keys to be received if (cell != null && cell == WindowFrame.getCurrentCell()) textArea.requestFocus(); textArea.paint(g); } /** * Method to update the font information in this window. */ public void updateFontInformation() { textArea.setFont(new Font(User.getDefaultTextCellFont(), 0, User.getDefaultTextCellSize())); } /** * Class to handle special changes to changes to the text. */ private static class TextWindowDocumentListener implements DocumentListener, FocusListener { private TextWindow tw; TextWindowDocumentListener(TextWindow tw) { this.tw = tw; } public void changedUpdate(DocumentEvent e) { tw.textWindowContentChanged(); } public void insertUpdate(DocumentEvent e) { tw.textWindowContentChanged(); } public void removeUpdate(DocumentEvent e) { tw.textWindowContentChanged(); } public void focusGained(FocusEvent e) { Main top = (Main) Main.getCurrentJFrame(); top.getTheMenuBar().setIgnoreTextEditKeys(true); } public void focusLost(FocusEvent e) { Main top = (Main) Main.getCurrentJFrame(); top.getTheMenuBar().setIgnoreTextEditKeys(false); } } public void copy() { int startSelection = textArea.getSelectionStart(); int endSelection = textArea.getSelectionEnd(); try { String line = textArea.getText(startSelection, endSelection - startSelection); TextUtils.setTextOnClipboard(line); } catch (BadLocationException ex) {} } public void cut() { int startSelection = textArea.getSelectionStart(); int endSelection = textArea.getSelectionEnd(); try { String line = textArea.getText(startSelection, endSelection - startSelection); TextUtils.setTextOnClipboard(line); textArea.replaceRange("", startSelection, endSelection); } catch (BadLocationException ex) {} } public void paste() { String replaceThis = TextUtils.getTextOnClipboard(); int startSelection = textArea.getSelectionStart(); int endSelection = textArea.getSelectionEnd(); textArea.replaceRange(replaceThis, startSelection, endSelection); } private void textWindowContentChanged() { if (!reloading) new SaveCellText(this); } public List<MutableTreeNode> loadExplorerTrees() { return wf.loadDefaultExplorerTree(); } public void loadTechnologies() { } /** * Method to return the top-level JPanel for this TextWindow. * @return the top-level JPanel for this TextWindow. */ public JPanel getPanel() { return overall; } /** * Method to get rid of this EditWindow. Called by WindowFrame when * that windowFrame gets closed. */ public void finished() {} /** * Class to save a cell's text in a new Job. */ private static class SaveCellText extends Job { private Cell cell; private String [] strings; private SaveCellText(TextWindow tw) { super("Save Cell Text", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER); this.cell = tw.cell; this.strings = tw.convertToStrings(); startJob(); } public boolean doIt() throws JobException { if (cell != null) cell.setTextViewContents(strings); return true; } } /** * Method to set the window title. */ public void setWindowTitle() { if (wf == null) return; wf.setTitle(wf.composeTitle(cell, "", 0)); } /** * Method to return the cell that is shown in this window. * @return the cell that is shown in this window. */ public Cell getCell() { return cell; } public Highlighter getHighlighter() { return null; } /** * Method to set the cell that is shown in the window to "cell". */ public void setCell(Cell cell, VarContext context, WindowFrame.DisplayAttributes displayAttributes) { this.cell = cell; String [] lines = (cell != null) ? cell.getTextViewContents() : null; String oneLine = (lines != null) ? oneLine = makeOneString(lines) : ""; setCellFont(cell); reloading = true; textArea.setText(oneLine); textArea.setSelectionStart(0); textArea.setSelectionEnd(0); reloading = false; setWindowTitle(); } /** * Method to read a text disk file into this TextWindow. */ public static void readTextCell() { WindowFrame wf = WindowFrame.getCurrentWindowFrame(); if (wf == null) return; WindowContent content = wf.getContent(); if (!(content instanceof TextWindow)) { Job.getUserInterface().showErrorMessage("You must first be editing a text cell (a cell whose view is textual, such as 'doc').", "Cannot import text file"); return; } TextWindow tw = (TextWindow)content; String fileName = OpenFile.chooseInputFile(FileType.TEXT, null); if (fileName == null) return; tw.readTextCell(fileName); } public void readTextCell(String fileName) { if (fileName == null) { System.out.println("Bad file name: "+fileName); return; } // start a job to do the input URL fileURL = TextUtils.makeURLToFile(fileName); InputStream stream = TextUtils.getURLStream(fileURL); if (stream == null) { System.out.println("Could not open file: " + fileURL.getFile()); return; } try { fileURL.openConnection(); } catch (IOException e) { System.out.println("Could not find file: " + fileURL.getFile()); return; } // clear the buffer textArea.setText(""); final int READ_BUFFER_SIZE = 65536; char [] buf = new char[READ_BUFFER_SIZE]; InputStreamReader is = new InputStreamReader(stream); StringBuilder sb = new StringBuilder(); try { for(;;) { int amtRead = is.read(buf, 0, READ_BUFFER_SIZE); if (amtRead <= 0) break; sb.append(buf, 0, amtRead); } stream.close(); } catch (IOException e) { System.out.println("Error reading the file"); } textArea.append(sb.toString()); } /** * Method to save this TextWindow to a disk file. */ public static void writeTextCell() { WindowFrame wf = WindowFrame.getCurrentWindowFrame(); if (wf == null) return; WindowContent content = wf.getContent(); if (!(content instanceof TextWindow)) { Job.getUserInterface().showErrorMessage("You must first be editing a text cell (a cell whose view is textual, such as 'doc').", "Cannot import text file"); return; } TextWindow tw = (TextWindow)content; Cell cell = tw.getCell(); String filePath = cell.getName() + ".txt"; String fileName = OpenFile.chooseOutputFile(FileType.TEXT, null, filePath); if (fileName == null) return; tw.writeTextCell(fileName); } /** * Method to write text cell into a file * @param fileName * @return true if no errors were found */ public boolean writeTextCell(String fileName) { if (fileName == null) { System.out.println("Bad filename: "+fileName); return false; } try { PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); Document doc = textArea.getDocument(); Element paragraph = doc.getDefaultRootElement(); int lines = paragraph.getElementCount(); for(int i=0; i<lines; i++) { Element e = paragraph.getElement(i); int startPos = e.getStartOffset(); int endPos = e.getEndOffset(); try { String line = textArea.getText(startPos, endPos - startPos); printWriter.print(line); } catch (BadLocationException ex) {} } printWriter.close(); } catch (IOException e) { System.out.println("Error saving " + fileName); return false; } System.out.println("Wrote " + fileName); return true; } /** * Method to select a line number in this TextWindow. * @param lineNumber the line to select (1-based). */ public void goToLineNumber(int lineNumber) { Document doc = textArea.getDocument(); Element paragraph = doc.getDefaultRootElement(); int lines = paragraph.getElementCount(); if (lineNumber <= 0 || lineNumber > lines) { System.out.println("Line numbers must be between 1 and "+lines); return; } Element e = paragraph.getElement(lineNumber-1); int startPos = e.getStartOffset(); int endPos = e.getEndOffset(); textArea.setSelectionStart(startPos); textArea.setSelectionEnd(endPos); } private void updateText(String [] strings) { textArea.setText(makeOneString(strings)); } /** * Method to update text for a cell (if it is being displayed). * This is called when the text for a cell has been changed by some other part of the system, * and should be redisplayed where appropriate. * @param cell the Cell whose text changed. */ public static void updateText(Cell cell) { for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); WindowContent content = wf.getContent(); if (content instanceof TextWindow) { if (content.getCell() == cell) { TextWindow tw = (TextWindow)content; if (!tw.finishing) { String [] strings = cell.getTextViewContents(); tw.updateText(strings); } } } } } /** * Method to convert an array of strings to a single string. * @param strings the array of strings. * @return the single string. */ private static String makeOneString(String [] strings) { StringBuffer sb = new StringBuffer(); int len = strings.length; for(int i=0; i<len; i++) { sb.append(strings[i]); sb.append("\n"); } return sb.toString(); } /** * Method to return the number of lines of text in this TextWindow. * @return the number of lines of text in this TextWindow. */ public int getLineCount() { Document doc = textArea.getDocument(); Element paragraph = doc.getDefaultRootElement(); int lines = paragraph.getElementCount(); return lines; } /** * Method to convert the document in this window to an array of strings. * @return an array of strings with the current text. */ public String [] convertToStrings() { Document doc = textArea.getDocument(); Element paragraph = doc.getDefaultRootElement(); int lines = paragraph.getElementCount(); String [] strings = new String[lines]; for(int i=0; i<lines; i++) { Element e = paragraph.getElement(i); int startPos = e.getStartOffset(); int endPos = e.getEndOffset(); try { strings[i] = textArea.getText(startPos, endPos - startPos - 1); } catch (BadLocationException ex) {} } return strings; } public void rightScrollChanged(int value) {} public void bottomScrollChanged(int value) {} public void repaint() {} public void fullRepaint() {} // /** Returns true if we can go back in history list, false otherwise */ // public boolean cellHistoryCanGoBack() { return false; } // // /** Returns true if we can go forward in history list, false otherwise */ // public boolean cellHistoryCanGoForward() { return false; } // public void cellHistoryGoBack() {} // // public void cellHistoryGoForward() {} /** * Method to pan and zoom the screen so that the entire cell is displayed. */ public void fillScreen() {} public void zoomOutContents() {} public void zoomInContents() {} public void focusOnHighlighted() {} private String searchString = null; private boolean searchCaseSensitive = false; /** * Method to initialize for a new text search. * @param search the string to locate. * @param caseSensitive true to match only where the case is the same. * @param regExp true if the search string is a regular expression. * @param whatToSearch a collection of text types to consider. * @param codeRestr a restriction on types of Code to consider (null to consider all Code values). * @param unitRestr a restriction on types of Units to consider (null to consider all Unit values). * @param highlightedOnly true to search only in the highlighted area. */ public void initTextSearch(String search, boolean caseSensitive, boolean regExp, Set<TextUtils.WhatToSearch> whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, boolean highlightedOnly) { if (regExp) System.out.println("Text windows don't yet implement Regular Expression matching"); searchString = search; searchCaseSensitive = caseSensitive; } /** * Method to find the next occurrence of a string. * @param reverse true to find in the reverse direction. * @return true if something was found. */ public boolean findNextText(boolean reverse) { Document doc = textArea.getDocument(); Element paragraph = doc.getDefaultRootElement(); int lines = paragraph.getElementCount(); int lineNo = 0; int searchPoint = textArea.getSelectionEnd(); if (reverse) searchPoint = textArea.getSelectionStart(); try { lineNo = textArea.getLineOfOffset(searchPoint); } catch (BadLocationException e) { return false; } for(int i=0; i<=lines; i++) { int index = lineNo + i; if (reverse) index = lineNo - i + lines; Element e = paragraph.getElement(index % lines); int startPos = e.getStartOffset(); int endPos = e.getEndOffset(); if (i == 0) { if (reverse) endPos = searchPoint+1; else startPos = searchPoint; } String theLine = null; try { theLine = textArea.getText(startPos, endPos - startPos - 1); } catch (BadLocationException ex) { return false; } int foundPos = TextUtils.findStringInString(theLine, searchString, 0, searchCaseSensitive, reverse); if (foundPos >= 0) { textArea.setSelectionStart(startPos + foundPos); textArea.setSelectionEnd(startPos + foundPos + searchString.length()); return true; } } return false; } /** * Method to replace the text that was just selected with findNextText(). * @param replace the new text to replace. */ public void replaceText(String replace) { int startSelection = textArea.getSelectionStart(); int endSelection = textArea.getSelectionEnd(); textArea.replaceRange(replace, startSelection, endSelection); } /** * Method to replace all selected text. * @param replace the new text to replace everywhere. */ public void replaceAllText(String replace) { Document doc = textArea.getDocument(); Element paragraph = doc.getDefaultRootElement(); int lines = paragraph.getElementCount(); for(int i=0; i<lines; i++) { Element e = paragraph.getElement(i); int startPos = e.getStartOffset(); int endPos = e.getEndOffset()-1; String theLine = null; try { theLine = textArea.getText(startPos, endPos - startPos); } catch (BadLocationException ex) { return; } boolean found = false; int scanPos = 0; for(;;) { int foundPos = TextUtils.findStringInString(theLine, searchString, scanPos, searchCaseSensitive, false); if (foundPos < 0) break; theLine = theLine.substring(0, foundPos) + replace + theLine.substring(foundPos+searchString.length()); scanPos = foundPos + replace.length(); found = true; } if (found) textArea.replaceRange(theLine, startPos, endPos); } } /** * Method to export directly PNG file * @param ep printable object. * @param filePath */ public void writeImage(ElectricPrinter ep, String filePath) { System.out.println("TextWindow:writeImage not implemented"); } /** * Method to intialize for printing. * @param ep the ElectricPrinter object. * @param pageFormat information about the print job. * @return Always true. */ public boolean initializePrinting(ElectricPrinter ep, PageFormat pageFormat) { int pageWid = (int)pageFormat.getImageableWidth() * ep.getDesiredDPI() / 72; int pageHei = (int)pageFormat.getImageableHeight() * ep.getDesiredDPI() / 72; overall.setSize(pageWid, pageHei); overall.validate(); overall.repaint(); return true; } /** * Method to print window using offscreen canvas. * @param ep printable object. * @return the image to print (null on error). */ public BufferedImage getPrintImage(ElectricPrinter ep) { return null; } /** * Method to pan along X or Y according to fixed amount of ticks * @param direction * @param panningAmounts * @param ticks */ public void panXOrY(int direction, double[] panningAmounts, int ticks) { // Nothing in this case } /** * Method to shift the window so that the current cursor location becomes the center. */ public void centerCursor() { // Nothing in this case } }