|
TextPanel |
|
package ij.text; import java.awt.*; import java.io.*; import java.awt.event.*; import java.util.*; import java.awt.datatransfer.*; import ij.*; import ij.plugin.filter.Analyzer; import ij.io.SaveDialog; import ij.measure.ResultsTable; import ij.util.Tools; import ij.plugin.frame.Recorder; /** This is an unlimited size text panel with tab-delimited, labeled and resizable columns. It is based on the hGrid class at http://www.lynx.ch/contacts/~/thomasm/Grid/index.html. */ public class TextPanel extends Panel implements AdjustmentListener, MouseListener, MouseMotionListener, KeyListener, ClipboardOwner, ActionListener, MouseWheelListener, Runnable { static final int DOUBLE_CLICK_THRESHOLD = 650; // height / width int iGridWidth,iGridHeight; int iX,iY; // data String[] sColHead; Vector vData; int[] iColWidth; int iColCount,iRowCount; int iRowHeight,iFirstRow; // scrolling Scrollbar sbHoriz,sbVert; int iSbWidth,iSbHeight; boolean bDrag; int iXDrag,iColDrag; boolean headings = true; String title = ""; String labels; KeyListener keyListener; Cursor resizeCursor = new Cursor(Cursor.E_RESIZE_CURSOR); Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR); int selStart=-1, selEnd=-1,selOrigin=-1, selLine=-1; TextCanvas tc; PopupMenu pm; boolean columnsManuallyAdjusted; long mouseDownTime; String filePath; ResultsTable rt; boolean unsavedLines; /** Constructs a new TextPanel. */ public TextPanel() { tc = new TextCanvas(this); setLayout(new BorderLayout()); add("Center",tc); sbHoriz=new Scrollbar(Scrollbar.HORIZONTAL); sbHoriz.addAdjustmentListener(this); sbHoriz.setFocusable(false); // prevents scroll bar from blinking on Windows add("South", sbHoriz); sbVert=new Scrollbar(Scrollbar.VERTICAL); sbVert.addAdjustmentListener(this); sbVert.setFocusable(false); ImageJ ij = IJ.getInstance(); if (ij!=null) { sbHoriz.addKeyListener(ij); sbVert.addKeyListener(ij); } add("East", sbVert); addPopupMenu(); } /** Constructs a new TextPanel. */ public TextPanel(String title) { this(); if (title.equals("Results")) { pm.addSeparator(); addPopupItem("Clear Results"); addPopupItem("Summarize"); addPopupItem("Distribution..."); addPopupItem("Set Measurements..."); addPopupItem("Duplicate..."); } } void addPopupMenu() { pm=new PopupMenu(); addPopupItem("Save As..."); pm.addSeparator(); addPopupItem("Cut"); addPopupItem("Copy"); addPopupItem("Clear"); addPopupItem("Select All"); add(pm); } void addPopupItem(String s) { MenuItem mi=new MenuItem(s); mi.addActionListener(this); pm.add(mi); } /** Clears this TextPanel and sets the column headings to those in the tab-delimited 'headings' String. Set 'headings' to "" to use a single column with no headings. */ public synchronized void setColumnHeadings(String labels) { boolean sameLabels = labels.equals(this.labels); this.labels = labels; if (labels.equals("")) { iColCount = 1; sColHead=new String[1]; sColHead[0] = ""; } else { sColHead = Tools.split(labels, "\t"); iColCount = sColHead.length; } flush(); vData=new Vector(); if (!(iColWidth!=null && iColWidth.length==iColCount && sameLabels && iColCount!=1)) { iColWidth=new int[iColCount]; columnsManuallyAdjusted = false; } iRowCount=0; resetSelection(); adjustHScroll(); tc.repaint(); } /** Returns the column headings as a tab-delimited string. */ public String getColumnHeadings() { return labels==null?"":labels; } public void setFont(Font font, boolean antialiased) { tc.fFont = font; tc.iImage = null; tc.fMetrics = null; tc.antialiased = antialiased; iColWidth[0] = 0; if (isShowing()) updateDisplay(); } /** Adds a single line to the end of this TextPanel. */ public void appendLine(String data) { if (vData==null) setColumnHeadings(""); char[] chars = data.toCharArray(); vData.addElement(chars); iRowCount++; if (isShowing()) { if (iColCount==1 && tc.fMetrics!=null) { iColWidth[0] = Math.max(iColWidth[0], tc.fMetrics.charsWidth(chars,0,chars.length)); adjustHScroll(); } updateDisplay(); unsavedLines = true; } } /** Adds one or more lines to the end of this TextPanel. */ public void append(String data) { if (data==null) data="null"; if (vData==null) setColumnHeadings(""); while (true) { int p=data.indexOf('\n'); if (p<0) { appendWithoutUpdate(data); break; } appendWithoutUpdate(data.substring(0,p)); data = data.substring(p+1); if (data.equals("")) break; } if (isShowing()) { updateDisplay(); unsavedLines = true; } } /** Adds a single line to the end of this TextPanel without updating the display. */ void appendWithoutUpdate(String data) { char[] chars = data.toCharArray(); vData.addElement(chars); iRowCount++; } void updateDisplay() { iY=iRowHeight*(iRowCount+1); adjustVScroll(); if (iColCount>1 && iRowCount<=10 && !columnsManuallyAdjusted) iColWidth[0] = 0; // forces column width calculation tc.repaint(); } String getCell(int column, int row) { if (column<0||column>=iColCount||row<0||row>=iRowCount) return null; return new String(tc.getChars(column, row)); } synchronized void adjustVScroll() { if(iRowHeight==0) return; Dimension d = tc.getSize(); int value = iY/iRowHeight; int visible = d.height/iRowHeight; int maximum = iRowCount+1; if (visible<0) visible=0; if (visible>maximum) visible=maximum; if (value>(maximum-visible)) value=maximum-visible; sbVert.setValues(value,visible,0,maximum); iY=iRowHeight*value; } synchronized void adjustHScroll() { if(iRowHeight==0) return; Dimension d = tc.getSize(); int w=0; for(int i=0;i<iColCount;i++) w+=iColWidth[i]; iGridWidth=w; sbHoriz.setValues(iX,d.width,0,iGridWidth); iX=sbHoriz.getValue(); } public void adjustmentValueChanged (AdjustmentEvent e) { iX=sbHoriz.getValue(); iY=iRowHeight*sbVert.getValue(); tc.repaint(); } public void mousePressed (MouseEvent e) { int x=e.getX(), y=e.getY(); if (e.isPopupTrigger() || e.isMetaDown()) pm.show(e.getComponent(),x,y); else if (e.isShiftDown()) extendSelection(x, y); else { select(x, y); handleDoubleClick(); } } void handleDoubleClick() { if (selStart<0 || selStart!=selEnd || iColCount!=1) return; boolean doubleClick = System.currentTimeMillis()-mouseDownTime<=DOUBLE_CLICK_THRESHOLD; mouseDownTime = System.currentTimeMillis(); if (doubleClick) { char[] chars = (char[])(vData.elementAt(selStart)); String s = new String(chars); int index = s.indexOf(": "); if (index>-1 && !s.endsWith(": ")) s = s.substring(index+2); // remove sequence number added by ListFilesRecursively if (s.indexOf(File.separator)!=-1 || s.indexOf(".")!=-1) { filePath = s; Thread thread = new Thread(this, "Open"); thread.setPriority(thread.getPriority()-1); thread.start(); } } } /** For better performance, open double-clicked files on separate thread instead of on event dispatch thread. */ public void run() { if (filePath!=null) IJ.open(filePath); } public void mouseExited (MouseEvent e) { if(bDrag) { setCursor(defaultCursor); bDrag=false; } } public void mouseMoved (MouseEvent e) { int x=e.getX(), y=e.getY(); if(y<=iRowHeight) { int xb=x; x=x+iX-iGridWidth; int i=iColCount-1; for(;i>=0;i--) { if(x>-7 && x<7) break; x+=iColWidth[i]; } if(i>=0) { if(!bDrag) { setCursor(resizeCursor); bDrag=true; iXDrag=xb-iColWidth[i]; iColDrag=i; } return; } } if(bDrag) { setCursor(defaultCursor); bDrag=false; } } public void mouseDragged (MouseEvent e) { if (e.isPopupTrigger() || e.isMetaDown()) return; int x=e.getX(), y=e.getY(); if(bDrag && x<tc.getSize().width) { int w=x-iXDrag; if(w<0) w=0; iColWidth[iColDrag]=w; columnsManuallyAdjusted = true; adjustHScroll(); tc.repaint(); } else { extendSelection(x, y); } } public void mouseReleased (MouseEvent e) {} public void mouseClicked (MouseEvent e) {} public void mouseEntered (MouseEvent e) {} public void mouseWheelMoved(MouseWheelEvent event) { synchronized(this) { int rot = event.getWheelRotation(); sbVert.setValue(sbVert.getValue()+rot); iY=iRowHeight*sbVert.getValue(); tc.repaint(); } } /** Unused keyPressed and keyTyped events will be passed to 'listener'.*/ public void addKeyListener(KeyListener listener) { keyListener = listener; } public void keyPressed(KeyEvent e) { //boolean cutCopyOK = (e.isControlDown()||e.isMetaDown()) // && selStart!=-1 && selEnd!=-1; //if (cutCopyOK && e.getKeyCode()==KeyEvent.VK_C) // copySelection(); //else if (cutCopyOK && e.getKeyCode()==KeyEvent.VK_X) // {if (copySelection()>0) clearSelection();} //else if (cutCopyOK && e.getKeyCode()==KeyEvent.VK_A) // selectAll(); //else if (keyListener!=null) // keyListener.keyPressed(e); int key = e.getKeyCode(); if (keyListener!=null&&key!=KeyEvent.VK_S&& key!=KeyEvent.VK_C && key!=KeyEvent.VK_X&& key!=KeyEvent.VK_A) keyListener.keyPressed(e); } public void keyReleased (KeyEvent e) {} public void keyTyped (KeyEvent e) { if (keyListener!=null) keyListener.keyTyped(e); } public void actionPerformed (ActionEvent e) { String cmd=e.getActionCommand(); doCommand(cmd); } void doCommand(String cmd) { if (cmd==null) return; if (cmd.equals("Save As...")) saveAs(""); else if (cmd.equals("Cut")) cutSelection(); else if (cmd.equals("Copy")) copySelection(); else if (cmd.equals("Clear")) clearSelection(); else if (cmd.equals("Select All")) selectAll(); else if (cmd.equals("Duplicate...")) duplicate(); else if (cmd.equals("Summarize")) IJ.doCommand("Summarize"); else if (cmd.equals("Distribution...")) IJ.doCommand("Distribution..."); else if (cmd.equals("Clear Results")) IJ.doCommand("Clear Results"); else if (cmd.equals("Set Measurements...")) IJ.doCommand("Set Measurements..."); else if (cmd.equals("Set File Extension...")) IJ.doCommand("Input/Output..."); } public void lostOwnership (Clipboard clip, Transferable cont) {} void duplicate() { if (rt==null) return; ResultsTable rt2 = (ResultsTable)rt.clone(); String title2 = IJ.getString("Title:", "Results2"); if (!title2.equals("")) { if (title2.equals("Results")) title2 = "Results2"; rt2.show(title2); } } void select(int x,int y) { Dimension d = tc.getSize(); if(iRowHeight==0 || x>d.width || y>d.height) return; int r=(y/iRowHeight)-1+iFirstRow; int lineWidth = iGridWidth; if (iColCount==1 && tc.fMetrics!=null && r>=0 && r<iRowCount) { char[] chars = (char[])vData.elementAt(r); lineWidth = Math.max(tc.fMetrics.charsWidth(chars,0,chars.length), iGridWidth); } if(r>=0 && r<iRowCount && x<lineWidth) { selOrigin = r; selStart = r; selEnd = r; } else { resetSelection(); selOrigin = r; if (r>=iRowCount) selOrigin = iRowCount-1; //System.out.println("select: "+selOrigin); } tc.repaint(); selLine=r; } void extendSelection(int x,int y) { Dimension d = tc.getSize(); if(iRowHeight==0 || x>d.width || y>d.height) return; int r=(y/iRowHeight)-1+iFirstRow; //System.out.println(r+" "+selOrigin); if(r>=0 && r<iRowCount) { if (r<selOrigin) { selStart = r; selEnd = selOrigin; } else { selStart = selOrigin; selEnd = r; } } tc.repaint(); selLine=r; } /** Copies the current selection to the system clipboard. Returns the number of characters copied. */ public int copySelection() { if (Recorder.record && title.equals("Results")) Recorder.record("String.copyResults"); if (selStart==-1 || selEnd==-1) return copyAll(); StringBuffer sb = new StringBuffer(); for (int i=selStart; i<=selEnd; i++) { char[] chars = (char[])(vData.elementAt(i)); sb.append(chars); if (i<selEnd || selEnd>selStart) sb.append('\n'); } String s = new String(sb); Clipboard clip = getToolkit().getSystemClipboard(); if (clip==null) return 0; StringSelection cont = new StringSelection(s); clip.setContents(cont,this); if (s.length()>0) { IJ.showStatus((selEnd-selStart+1)+" lines copied to clipboard"); if (this.getParent() instanceof ImageJ) Analyzer.setUnsavedMeasurements(false); } return s.length(); } int copyAll() { selectAll(); int count = selEnd - selStart; if (count>0) copySelection(); resetSelection(); unsavedLines = false; return count; } void cutSelection() { if (selStart==-1 || selEnd==-1) selectAll(); copySelection(); clearSelection(); } /** Deletes the selected lines. */ public void clearSelection() { if (selStart==-1 || selEnd==-1) { if (getLineCount()>0) IJ.error("Selection required"); return; } if (selStart==0 && selEnd==(iRowCount-1)) { vData.removeAllElements(); iRowCount = 0; if (rt!=null) { if (IJ.isResultsWindow() && IJ.getTextPanel()==this) { Analyzer.setUnsavedMeasurements(false); Analyzer.resetCounter(); } else rt.reset(); } } else { int rowCount = iRowCount; boolean atEnd = rowCount-selEnd<8; int count = selEnd-selStart+1; for (int i=0; i<count; i++) { vData.removeElementAt(selStart); iRowCount--; } if (rt!=null && rowCount==rt.getCounter()) { for (int i=0; i<count; i++) rt.deleteRow(selStart); rt.show(title); if (!atEnd) { iY = 0; tc.repaint(); } } } selStart=-1; selEnd=-1; selOrigin=-1; selLine=-1; adjustVScroll(); tc.repaint(); } /** Deletes all the lines. */ public void clear() { if (vData==null) return; vData.removeAllElements(); iRowCount = 0; selStart=-1; selEnd=-1; selOrigin=-1; selLine=-1; adjustVScroll(); tc.repaint(); } /** Selects all the lines in this TextPanel. */ public void selectAll() { if (selStart==0 && selEnd==iRowCount-1) { resetSelection(); return; } selStart = 0; selEnd = iRowCount-1; selOrigin = 0; tc.repaint(); selLine=-1; } /** Clears the selection, if any. */ public void resetSelection() { selStart=-1; selEnd=-1; selOrigin=-1; selLine=-1; if (iRowCount>0) tc.repaint(); } /** Writes all the text in this TextPanel to a file. */ public void save(PrintWriter pw) { resetSelection(); if (labels!=null && !labels.equals("")) pw.println(labels); for (int i=0; i<iRowCount; i++) { char[] chars = (char[])(vData.elementAt(i)); pw.println(new String(chars)); } unsavedLines = false; } /** Saves all the text in this TextPanel to a file. Set 'path' to "" to display a save as dialog. Returns 'false' if the user cancels the save as dialog.*/ public boolean saveAs(String path) { boolean isResults = IJ.isResultsWindow() && IJ.getTextPanel()==this; if (path.equals("")) { IJ.wait(10); String ext = isResults?Prefs.get("options.ext", ".xls"):".txt"; SaveDialog sd = new SaveDialog("Save as Text", title, ext); String file = sd.getFileName(); if (file == null) return false; path = sd.getDirectory() + file; } PrintWriter pw = null; try { FileOutputStream fos = new FileOutputStream(path); BufferedOutputStream bos = new BufferedOutputStream(fos); pw = new PrintWriter(bos); } catch (IOException e) { //IJ.write("" + e); return true; } save(pw); pw.close(); if (isResults) { Analyzer.setUnsavedMeasurements(false); if (Recorder.record) Recorder.record("saveAs", "Measurements", path); } else { if (Recorder.record) Recorder.record("saveAs", "Text", path); } IJ.showStatus(""); return true; } /** Returns all the text as a string. */ public String getText() { StringBuffer sb = new StringBuffer(); if (labels!=null && !labels.equals("")) { sb.append(labels); sb.append('\n'); } for (int i=0; i<iRowCount; i++) { char[] chars = (char[])(vData.elementAt(i)); sb.append(chars); sb.append('\n'); } return new String(sb); } public void setTitle(String title) { this.title = title; } /** Returns the number of lines of text in this TextPanel. */ public int getLineCount() { return iRowCount; } /** Returns the specified line as a string. The argument must be greater than or equal to zero and less than the value returned by getLineCount(). */ public String getLine(int index) { if (index<0 || index>=iRowCount) throw new IllegalArgumentException("index out of range: "+index); return new String((char[])(vData.elementAt(index))); } /** Replaces the contents of the specified line, where 'index' must be greater than or equal to zero and less than the value returned by getLineCount(). */ public void setLine(int index, String s) { if (index<0 || index>=iRowCount) throw new IllegalArgumentException("index out of range: "+index); if (vData!=null) { vData.setElementAt(s.toCharArray(), index); tc.repaint(); } } /** Returns the index of the first selected line, or -1 if there is no slection. */ public int getSelectionStart() { return selStart; } /** Returns the index of the last selected line, or -1 if there is no slection. */ public int getSelectionEnd() { return selEnd; } /** Sets the ResultsTable associated with this TextPanel. */ public void setResultsTable(ResultsTable rt) { this.rt = rt; } void flush() { if (vData!=null) vData.removeAllElements(); vData = null; } }
|
TextPanel |
|