Initial commit of newLISP.
[newlisp.git] / guiserver / java / TextPaneWidget.java
blob05e088df2d451769840930da9f26302e203e3878
1 //
2 // TextPaneWidget.java
3 // guiserver
4 //
5 // Created by Lutz Mueller on 6/11/07.
6 //
7 //
8 // Copyright (C) 2007 Lutz Mueller
9 //
10 // This program is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License
21 // along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import java.awt.*;
26 import java.awt.event.*;
27 import java.util.*;
28 import java.io.File;
29 import java.io.FileReader;
30 import java.io.FileWriter;
32 import javax.swing.*;
33 import javax.swing.text.*;
34 import javax.swing.text.html.*;
35 import javax.swing.event.*;
36 import javax.swing.undo.*;
38 @SuppressWarnings("unchecked")
39 public class TextPaneWidget extends aTextWidget {
41 JTextPane textPane;
42 String contentType = null;
44 CaretListener caretListener;
45 CaretEvent lastCaretEvent = null;
46 int lastCharCode = 65535;
47 int lastModifiers = 0;
48 int documentLength = 0;
49 String undoState = "nil";
50 String redoState = "nil";
52 Color foreground = new Color(0,0,0);
53 StyledDocument styledDoc;
54 Vector shTopLevels;
55 TextPaneWidget widget;
57 static final int SYNTAX_NONE = 0;
58 static final int SYNTAX_NEWLISP = 1;
59 static final int SYNTAX_C = 2;
60 static final int SYNTAX_CPP = 3;
61 static final int SYNTAX_JAVA = 4;
62 static final int SYNTAX_PHP = 5;
64 int syntaxSelected = SYNTAX_NONE;
66 //undo helpers
67 protected UndoAction undoAction;
68 protected RedoAction redoAction;
69 protected UndoManager undo = new UndoManager();
70 MyUndoableEditListener undoableEditListener;
71 boolean undoEnabled = true;
73 public TextPaneWidget(StringTokenizer params)
75 id = params.nextToken();
76 action = params.nextToken();
78 textPane = new JTextPane();
79 textPane.setDoubleBuffered(true);
81 Caret caret = new MyCaret();
82 caret.setBlinkRate(500);
84 textPane.setCaret(caret);
86 if(params.hasMoreTokens())
87 contentType = params.nextToken();
88 if(!contentType.equals("text/html") && !contentType.equals("text/rtf"))
89 contentType = "text/plain";
90 textPane.setContentType(contentType);
92 areaScrollPane = new JScrollPane(textPane);
93 areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
95 container = areaScrollPane;
96 jcomponent = textPane;
97 component = areaScrollPane;
98 textcomp = textPane;
99 isScrollable = true;
101 widget = this;
103 shTopLevels = new Vector();
105 if(params.hasMoreTokens())
106 areaScrollPane.setPreferredSize(
107 new Dimension (Integer.parseInt(params.nextToken()),Integer.parseInt(params.nextToken())));
109 gsObject.widgets.put(id, this);
111 styledDoc = textPane.getStyledDocument();
112 AbstractDocument doc = (AbstractDocument)styledDoc;
113 //doc.setDocumentFilter(new MyDocumentFilter(1000000));
115 KeyListener keyListener = new KeyAdapter() {
116 public void keyPressed(KeyEvent e)
118 Character chr = new Character(e.getKeyChar());
119 int code = e.getKeyCode();
120 lastCharCode = chr.hashCode();
121 int lastPos, caretPos, size, len;
123 lastModifiers = e.getModifiersEx();
125 if(syntaxSelected != SYNTAX_NONE) colorSyntax();
127 switch(lastCharCode)
129 case 40:
130 highlightClosingPar('(', ')');
131 break;
132 case 41:
133 highlightOpeningPar('(', ')');
134 break;
135 case 123:
136 highlightClosingPar('{', '}');
137 break;
138 case 125:
139 highlightOpeningPar('{', '}');
140 break;
141 case 91:
142 highlightClosingPar('[', ']');
143 break;
144 case 93:
145 highlightOpeningPar('[', ']');
146 break;
152 caretListener = new CaretListener() {
153 public void caretUpdate(CaretEvent ce)
155 int mark, dot;
157 if(ce == null) return;
159 mark = ce.getMark();
160 dot = ce.getDot();
162 lastCaretEvent = ce;
164 undoAction.updateUndoState();
165 redoAction.updateRedoState();
167 if(undoAction.isEnabled()) undoState = "true";
168 else undoState = "nil";
170 if(redoAction.isEnabled()) redoState = "true";
171 else redoState = "nil";
173 guiserver.out.println("(" + action + " \"" + id + "\" " + lastCharCode + " " +
174 lastModifiers + " " + dot + " " + mark + " " + documentLength + " " +
175 undoState + " " + redoState + ")");
176 guiserver.out.flush();
177 lastCharCode = 65535;
181 undoAction = new UndoAction();
182 redoAction = new RedoAction();
184 undoableEditListener = new MyUndoableEditListener();
186 styledDoc.addUndoableEditListener(undoableEditListener);
187 styledDoc.addDocumentListener(new MyDocumentListener());
189 // set ctrl-Z and meta-Z undo/redo keys
190 InputMap inputMap = textPane.getInputMap();
191 KeyStroke key;
193 if(guiserver.MAC_OS_X)
194 key = KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.META_MASK);
195 else
196 key = KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.CTRL_MASK);
197 inputMap.put(key, undoAction);
199 if(guiserver.MAC_OS_X)
200 key = KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.META_MASK | Event.SHIFT_MASK);
201 else
202 key = KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.CTRL_MASK | Event.SHIFT_MASK);
203 inputMap.put(key, redoAction);
205 textPane.addHyperlinkListener(new Hyperactive());
206 textPane.addKeyListener(keyListener);
207 textPane.addCaretListener(caretListener);
210 public void highlightOpeningPar(char opng, char clsng)
212 int balance = -1;
213 String text = textPane.getText();
214 int currentPos = textPane.getCaretPosition();
215 int caretPos = 0;
216 int len = text.length();
218 caretPos = currentPos - 1; // new
219 // go back looking for matching parenthesis
220 while(balance != 0 && caretPos >= 0)
222 if(text.charAt(caretPos) == opng) ++balance;
223 else if(text.charAt(caretPos) == clsng) --balance;
224 --caretPos;
227 ++caretPos;
229 if(balance == 0) highlightPosition(caretPos, currentPos);
232 public void highlightClosingPar(char opng, char clsng)
234 int balance = 1;
235 String text = textPane.getText();
236 int currentPos = textPane.getCaretPosition();
237 int caretPos = 0;
238 int len = text.length();
240 if(len == 0) return;
242 caretPos = currentPos;
244 // go forward to matching parentheseis
245 while(balance != 0 && caretPos < len)
247 if(text.charAt(caretPos) == opng) ++balance;
248 else if(text.charAt(caretPos) == clsng) --balance;
249 ++caretPos;
252 if(balance == 0) highlightPosition(caretPos, currentPos);
255 public void highlightPosition(int pos, int oldPos)
257 textPane.setCaretPosition(pos);
258 textPane.getCaret().paint(textPane.getGraphics());
259 textPane.repaint();
260 try {Thread.sleep(400); } catch (InterruptedException ie) {}
261 textPane.setCaretPosition(oldPos);
262 textPane.getCaret().paint(textPane.getGraphics());
266 public void loadText(StringTokenizer tokens)
268 String path = Base64Coder.decodeString(tokens.nextToken());
270 EditorKit kit = textPane.getEditorKit();
272 System.setProperty("line.separator", "\n");
274 try {
275 if(guiserver.UTF8)
276 kit.read(new FilterFileReader(path, "UTF8"), styledDoc, 0);
277 else
278 kit.read(new FilterFileReader(path), styledDoc, 0);
280 catch(Exception ex) { ErrorDialog.show("gs:load-text", "Cannot load or decode file: " + path); }
284 public void saveText(StringTokenizer tokens)
286 String path = Base64Coder.decodeString(tokens.nextToken());
288 EditorKit kit = textPane.getEditorKit();
290 try { kit.write(new FileWriter(path), styledDoc, 0, styledDoc.getLength()); }
291 catch(Exception ex) { ErrorDialog.show("gs:save-text", "Cannot save file: " + path); }
295 public void findText(StringTokenizer tokens)
297 String findtext = Base64Coder.decodeString(tokens.nextToken());
298 String direction = "next";
299 boolean isRegex = false;
300 int dot;
302 String findTextAction = tokens.nextToken();
304 if(tokens.hasMoreTokens())
305 direction = tokens.nextToken();
307 if(tokens.hasMoreTokens())
308 isRegex = tokens.nextToken().equals("true");
310 String text = textPane.getText();
312 int currentCaret = textPane.getCaretPosition();
314 if(direction.equals("previous"))
316 //++currentCaret;
317 if(currentCaret > findtext.length() + 1)
318 currentCaret -= findtext.length() + 1;
319 dot = text.lastIndexOf(findtext, currentCaret);
321 else
322 dot = text.indexOf(findtext, currentCaret);
324 guiserver.out.println("(" + findTextAction + " \"" + id + "\" " + dot + ")");
325 guiserver.out.flush();
327 if(dot < 0)
329 textPane.setCaretPosition(currentCaret);
330 return;
333 textPane.setCaretPosition(dot);
334 textPane.moveCaretPosition(dot + findtext.length());
337 public void setTabSize(StringTokenizer tokens)
339 int tabSize = Integer.parseInt(tokens.nextToken());
341 TabSet tabSet = new TabSet(new TabStop[] {
342 new TabStop(tabSize), new TabStop(2 * tabSize), new TabStop(3 * tabSize),
343 new TabStop(4 * tabSize), new TabStop(5 * tabSize), new TabStop(6 * tabSize),
344 new TabStop(7 * tabSize), new TabStop(8 * tabSize), new TabStop(9 * tabSize) });
346 SimpleAttributeSet attributes = new SimpleAttributeSet();
347 StyleConstants.setTabSet(attributes, tabSet);
348 styledDoc.setParagraphAttributes(0, documentLength, attributes, false);
351 public void setSyntax(StringTokenizer tokens)
353 String tkn = tokens.nextToken();
355 if(tkn.equals("true") || tkn.equals("lsp"))
356 syntaxSelected = SYNTAX_NEWLISP;
357 else if(tkn.equals("c"))
358 syntaxSelected = SYNTAX_C;
359 else if(tkn.equals("cpp"))
360 syntaxSelected = SYNTAX_CPP;
361 else if(tkn.equals("java"))
362 syntaxSelected = SYNTAX_JAVA;
363 else if(tkn.equals("php"))
364 syntaxSelected = SYNTAX_PHP;
365 else syntaxSelected = SYNTAX_NONE;
367 //textPane.setCursor(new Cursor(Cursor.WAIT_CURSOR));
368 if(syntaxSelected == SYNTAX_NONE)
370 SimpleAttributeSet normal = new SimpleAttributeSet();
371 StyleConstants.setForeground(normal, widget.foreground);
372 styledDoc.setCharacterAttributes(0, documentLength, normal, true);
374 else
376 shTopLevels.removeAllElements();
377 if(syntaxSelected == SYNTAX_NEWLISP)
378 SyntaxHighlighter.color(widget, 0, documentLength);
379 else
381 SyntaxHighlighterC.setFlavor(syntaxSelected);
382 SyntaxHighlighterC.color(widget, 0, documentLength);
385 //textPane.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
387 undo.discardAllEdits();
390 public void setForeground(StringTokenizer tokens)
392 Float red = Float.parseFloat(tokens.nextToken());
393 Float green = Float.parseFloat(tokens.nextToken());
394 Float blue = Float.parseFloat(tokens.nextToken());
395 if(tokens.hasMoreTokens())
397 Float alpha = Float.parseFloat(tokens.nextToken());
398 textPane.setForeground(new Color(red, green, blue, alpha));
400 else
401 textPane.setForeground(new Color(red, green, blue));
403 widget.foreground = textPane.getForeground();
406 class Hyperactive implements HyperlinkListener {
407 public void hyperlinkUpdate(HyperlinkEvent e) {
408 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
409 JEditorPane pane = (JEditorPane) e.getSource();
410 if (e instanceof HTMLFrameHyperlinkEvent) {
411 HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
412 HTMLDocument doc = (HTMLDocument)pane.getDocument();
413 doc.processHTMLFrameHyperlinkEvent(evt);
414 } else {
415 try {
416 pane.setPage(e.getURL());
417 } catch (Throwable t) {
418 textPane.setText("The page could not be displayed.");
425 // undo and redo
427 public void undoEnable(StringTokenizer tokens)
429 undoEnabled = tokens.nextToken().equals("true");
432 public void undoText(StringTokenizer tokens)
434 undoAction.actionPerformed(new ActionEvent(textPane, Event.ACTION_EVENT, "Undo"));
435 //undo.undo();
438 public void redoText(StringTokenizer tokens)
440 redoAction.actionPerformed(new ActionEvent(textPane, Event.ACTION_EVENT, "Redo"));
443 public void colorSyntax()
445 int caretPos = textPane.getCaretPosition();
446 int pos = 0;
447 int size = shTopLevels.size();
448 int p, len;
450 for(int idx = size - 1; idx >= 0; idx--)
452 pos = (Integer)shTopLevels.elementAt(idx);
453 if(caretPos > pos)
455 for(int i = idx; i < size; i++)
457 p = (Integer)shTopLevels.elementAt(idx);
458 shTopLevels.removeElementAt(idx);
460 break;
464 if(documentLength > caretPos + 1024)
465 len = caretPos + 1024;
466 else
467 len = documentLength;
469 if(syntaxSelected == SYNTAX_NEWLISP)
470 SyntaxHighlighter.color(widget, pos, len);
471 else
472 SyntaxHighlighterC.color(widget, pos, len);
475 //This one listens for edits that can be undone.
476 protected class MyUndoableEditListener implements UndoableEditListener
478 public void undoableEditHappened(UndoableEditEvent e)
480 //Remember the edit and update the menus.
481 if(SyntaxHighlighter.active || undoEnabled != true) return;
482 undo.addEdit(e.getEdit());
483 //System.out.println("=>" + undo.getUndoPresentationName());
484 undoAction.updateUndoState();
485 redoAction.updateRedoState();
489 // Listens for any changes to the document.
490 protected class MyDocumentListener implements DocumentListener
492 public void insertUpdate(DocumentEvent e) { updateParams(e);}
493 public void removeUpdate(DocumentEvent e) { updateParams(e);}
494 public void changedUpdate(DocumentEvent e) { updateParams(e);}
496 private void updateParams(DocumentEvent e)
498 Document document = (Document)e.getDocument();
499 documentLength = document.getLength();
503 class UndoAction extends AbstractAction
505 public UndoAction() {
506 super("Undo");
507 setEnabled(false);
510 public void actionPerformed(ActionEvent e)
512 String pname = undo.getUndoPresentationName();
513 try { undo.undo(); } catch (Exception ex)
515 Toolkit.getDefaultToolkit().beep();
518 String qname = undo.getUndoPresentationName();
519 if(pname.equals("Undo addition") && qname.equals("Undo deletion"))
521 try { undo.undo(); } catch (Exception ex)
523 Toolkit.getDefaultToolkit().beep();
527 updateUndoState();
528 redoAction.updateRedoState();
529 caretListener.caretUpdate(lastCaretEvent);
532 protected void updateUndoState() {
533 if (undo.canUndo()) {
534 setEnabled(true);
535 putValue(Action.NAME, undo.getUndoPresentationName());
537 else {
538 setEnabled(false);
539 putValue(Action.NAME, "Undo");
544 class RedoAction extends AbstractAction
546 public RedoAction() {
547 super("Redo");
548 setEnabled(false);
551 public void actionPerformed(ActionEvent e)
553 String pname = undo.getRedoPresentationName();
554 try { undo.redo();} catch (Exception ex)
556 Toolkit.getDefaultToolkit().beep();
559 String qname = undo.getRedoPresentationName();
560 if(pname.equals("Redo deletion") && qname.equals("Redo addition"))
562 try { undo.redo(); } catch (Exception ex)
564 Toolkit.getDefaultToolkit().beep();
568 updateRedoState();
569 undoAction.updateUndoState();
570 caretListener.caretUpdate(lastCaretEvent);
573 protected void updateRedoState()
575 if (undo.canRedo()) {
576 setEnabled(true);
577 putValue(Action.NAME, undo.getRedoPresentationName());
579 else {
580 setEnabled(false);
581 putValue(Action.NAME, "Redo");
586 class MyCaret extends DefaultCaret
588 public void paint(Graphics g) {
589 if (!isVisible()) return;
590 try
592 JTextComponent c = getComponent();
593 int dot = getDot();
594 Rectangle r = c.modelToView(dot);
595 g.setColor(c.getCaretColor());
596 g.fillRect(r.x, r.y, 2, r.height);
598 catch (Exception e) { System.err.println("."); }
601 protected synchronized void damage(Rectangle r)
603 if (r == null) return;
604 x = r.x;
605 y = r.y;
606 width = 2;
607 height = r.height;
608 repaint();
614 // eof //