1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $
4  * $Revision: 7502 $
5  *
6  * Copyright (C) 2005  The Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library 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 GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 package org.jmol.console;
26 
27 import java.awt.event.KeyEvent;
28 import java.util.Hashtable;
29 import java.util.Map;
30 
31 import javajs.util.PT;
32 
33 import org.jmol.api.JmolAbstractButton;
34 import org.jmol.api.JmolAppConsoleInterface;
35 import org.jmol.api.JmolCallbackListener;
36 import org.jmol.api.JmolScriptEditorInterface;
37 import org.jmol.c.CBK;
38 import org.jmol.i18n.GT;
39 import org.jmol.script.T;
40 import org.jmol.viewer.JC;
41 import org.jmol.viewer.Viewer;
42 
43 public abstract class GenericConsole implements JmolAppConsoleInterface, JmolCallbackListener {
44 
45   protected GenericTextArea input;
46   protected GenericTextArea output;
47 
48   public Viewer vwr;
49 
setViewer(Viewer vwr)50   protected void setViewer(Viewer vwr) {
51     this.vwr = vwr;
52     if (labels == null) {
53       Map<String, String> l = new Hashtable<String, String>();
54       l.put("title", GT.$("Jmol Script Console") + " " + Viewer.getJmolVersion());
55       setupLabels(l);
56       labels = l;
57     }
58 
59   }
60 
61   protected static Map<String, String> labels;
62   protected Map<String, Object> menuMap = new Hashtable<String, Object>();
63   protected JmolAbstractButton editButton, runButton, historyButton, stateButton;
64   protected JmolAbstractButton clearOutButton, clearInButton, loadButton;
65 
isMenuItem(Object source)66   abstract protected boolean isMenuItem(Object source);
layoutWindow(String enabledButtons)67   abstract protected void layoutWindow(String enabledButtons);
setTitle()68   abstract protected void setTitle();
69   @Override
setVisible(boolean visible)70   abstract public void setVisible(boolean visible);
71   @Override
getScriptEditor()72   abstract public JmolScriptEditorInterface getScriptEditor();
73   @Override
dispose()74   abstract public void dispose();
75 
setButton(String text)76   abstract protected JmolAbstractButton setButton(String text);
77 
addButton(JmolAbstractButton b, String label)78   protected JmolAbstractButton addButton(JmolAbstractButton b, String label) {
79     b.addConsoleListener(this);
80     menuMap.put(label, b);
81     return b;
82   }
83 
getLabel1()84   protected JmolAbstractButton getLabel1() {
85     return null;
86   }
87 
setupLabels(Map<String, String> labels)88   protected void setupLabels(Map<String, String>  labels) {
89     // these three are for ImageDialog
90     labels.put("saveas", GT.$("&Save As..."));
91     labels.put("file", GT.$("&File"));
92     labels.put("close", GT.$("&Close"));
93     setupLabels0(labels);
94   }
95 
setupLabels0(Map<String, String> labels)96   protected void setupLabels0(Map<String, String>  labels) {
97     labels.put("help", GT.$("&Help"));
98     labels.put("search", GT.$("&Search..."));
99     labels.put("commands", GT.$("&Commands"));
100     labels.put("functions", GT.$("Math &Functions"));
101     labels.put("parameters", GT.$("Set &Parameters"));
102     labels.put("more", GT.$("&More"));
103     labels.put("Editor", GT.$("Editor"));
104     labels.put("State", GT.$("State"));
105     labels.put("Run", GT.$("Run"));
106     labels.put("Clear Output", GT.$("Clear Output"));
107     labels.put("Clear Input", GT.$("Clear Input"));
108     labels.put("History", GT.$("History"));
109     labels.put("Load", GT.$("Load"));
110     labels.put("label1", GT
111         .$("press CTRL-ENTER for new line or paste model data and press Load"));
112     labels.put("default",
113         GT.$("Messages will appear here. Enter commands in the box below. Click the console Help menu item for on-line help, which will appear in a new browser window."));
114   }
115 
setLabels()116   protected void setLabels() {
117     boolean doTranslate = GT.setDoTranslate(true);
118     editButton = setButton("Editor");
119     stateButton = setButton("State");
120     runButton = setButton("Run");
121     clearOutButton = setButton("Clear Output");
122     clearInButton = setButton("Clear Input");
123     historyButton = setButton("History");
124     loadButton = setButton("Load");
125     defaultMessage = getLabel("default");
126     setTitle();
127     GT.setDoTranslate(doTranslate);
128   }
129 
getLabel(String key)130   public static String getLabel(String key) {
131     return labels.get(key);
132   }
133 
displayConsole()134   protected void displayConsole() {
135     layoutWindow(null);
136     outputMsg(defaultMessage);
137   }
138 
139   protected String defaultMessage;
140   protected JmolAbstractButton label1;
141 
updateLabels()142   protected void updateLabels() {
143     return;
144   }
145 
nextFileName(String stub, int nTab)146   abstract protected String nextFileName(String stub, int nTab);
147   public int nTab = 0;
148   private String incompleteCmd;
149 
completeCommand(String thisCmd)150   public String completeCommand(String thisCmd) {
151     if (thisCmd.length() == 0)
152       return null;
153     String strCommand = (nTab <= 0 || incompleteCmd == null ? thisCmd
154         : incompleteCmd);
155     incompleteCmd = strCommand;
156     String[] splitCmd = GenericConsole.splitCommandLine(thisCmd);
157     if (splitCmd == null)
158       return null;
159     boolean asCommand = splitCmd[2] == null;
160     boolean inBrace = (splitCmd[3] != null);
161     String notThis = splitCmd[asCommand ? 1 : 2];
162     String s = splitCmd[1];
163     if (notThis.length() == 0)
164       return null;
165     T token = T.getTokenFromName(s.trim().toLowerCase());
166     int cmdtok = (token == null ? 0 : token.tok);
167     boolean isSelect = T.tokAttr(cmdtok, T.atomExpressionCommand);
168     splitCmd = GenericConsole.splitCommandLine(strCommand);
169     String cmd = null;
170     if (!asCommand && (notThis.charAt(0) == '"' || notThis.charAt(0) == '\'')) {
171       char q = notThis.charAt(0);
172       notThis = PT.trim(notThis, "\"\'");
173       String stub = PT.trim(splitCmd[2], "\"\'");
174       cmd = nextFileName(stub, nTab);
175       if (cmd != null)
176         cmd = splitCmd[0] + splitCmd[1] + q + cmd + q;
177     } else {
178       Map<String, Object> map = null;
179       if (!asCommand) {
180         notThis = s;
181         if (inBrace || splitCmd[2].startsWith("$")
182             //|| T.isIDcmd(cmdtok)
183             || isSelect) {
184           map = new Hashtable<String, Object>();
185           vwr.getObjectMap(map, inBrace || isSelect ? '{' : splitCmd[2].startsWith("$") ? '$' : '0');
186         }
187       }
188       cmd = T.completeCommand(map, s.equalsIgnoreCase("set "), asCommand, asCommand ? splitCmd[1]
189           : splitCmd[2], nTab);
190       cmd = splitCmd[0]
191           + (cmd == null ? notThis : asCommand ? cmd : splitCmd[1] + cmd);
192     }
193     return (cmd == null || cmd.equals(strCommand) ? null : cmd);
194   }
195 
doAction(Object source)196   protected void doAction(Object source) {
197     if (source == runButton) {
198       execute(null);
199     } else if (source == editButton) {
200       vwr.getProperty("DATA_API","scriptEditor", null);
201     } else if (source == historyButton) {
202       clearContent(vwr.getSetHistory(Integer.MAX_VALUE));
203     } else if (source == stateButton) {
204       clearContent(vwr.getStateInfo());
205       // problem here is that in some browsers, you cannot clip from
206       // the editor.
207       //vwr.getProperty("DATA_API","scriptEditor", new String[] { "current state" , vwr.getStateInfo() });
208     } else
209       if (source == clearInButton) {
210         input.setText("");
211         return;
212       }
213       if (source == clearOutButton) {
214         output.setText("");
215         return;
216       }
217       if (source == loadButton) {
218         vwr.loadInlineAppend(input.getText(), false);
219         return;
220       }
221       if (isMenuItem(source)) {
222         execute(((JmolAbstractButton) source).getName());
223         return;
224       }
225   }
226 
execute(String strCommand)227   protected void execute(String strCommand) {
228     String cmd = (strCommand == null ? input.getText() : strCommand);
229     if (strCommand == null)
230       input.setText(null);
231     String strErrorMessage = vwr.script(cmd + JC.SCRIPT_EDITOR_IGNORE);
232     if (strErrorMessage != null && !strErrorMessage.equals("pending"))
233       outputMsg(strErrorMessage);
234   }
235 
destroyConsole()236   protected void destroyConsole() {
237     // if the vwr is an applet, when we close the console
238     // we
239     if (vwr.isApplet)
240       vwr.getProperty("DATA_API", "getAppConsole", Boolean.FALSE);
241   }
242 
setAbstractButtonLabels(Map<String, Object> menuMap, Map<String, String> labels)243   public static void setAbstractButtonLabels(Map<String, Object> menuMap,
244                                Map<String, String> labels) {
245     for (String key: menuMap.keySet()) {
246       JmolAbstractButton m = (JmolAbstractButton) menuMap.get(key);
247       String label = labels.get(key);
248       if (key.indexOf("Tip") == key.length() - 3) {
249         m.setToolTipText(labels.get(key));
250       } else {
251         char mnemonic = getMnemonic(label);
252         if (mnemonic != ' ')
253           m.setMnemonic(mnemonic);
254         label = getLabelWithoutMnemonic(label);
255         m.setText(label);
256       }
257     }
258   }
259 
getLabelWithoutMnemonic(String label)260   public static String getLabelWithoutMnemonic(String label) {
261     if (label == null) {
262       return null;
263     }
264     int index = label.indexOf('&');
265     if (index == -1) {
266       return label;
267     }
268     return label.substring(0, index) +
269       ((index < label.length() - 1) ? label.substring(index + 1) : "");
270   }
271 
getMnemonic(String label)272   static char getMnemonic(String label) {
273     if (label == null) {
274       return ' ';
275     }
276     int index = label.indexOf('&');
277     if ((index == -1) || (index == label.length() - 1)){
278       return ' ';
279     }
280     return label.charAt(index + 1);
281   }
282 
map(Object button, String key, String label, Map<String, Object> menuMap)283   public static void map(Object button, String key, String label,
284                          Map<String, Object> menuMap) {
285     char mnemonic = getMnemonic(label);
286     if (mnemonic != ' ')
287       ((JmolAbstractButton) button).setMnemonic(mnemonic);
288     if (menuMap != null) {
289       if (key.indexOf("NMR.")>=0)System.out.println("genericconsole mapping " + key + " to " + label);
290       menuMap.put(key, button);
291     }
292   }
293 
294   ///////////// JmolCallbackListener interface
295 
296   // Allowing for just the callbacks needed to provide status feedback to the console.
297   // For applications that embed Jmol, see the example application Integration.java.
298 
299   @Override
notifyEnabled(CBK type)300   public boolean notifyEnabled(CBK type) {
301     // See org.jmol.viewer.JmolConstants.java for a complete list
302     switch (type) {
303     case ECHO:
304     case MEASURE:
305     case MESSAGE:
306     case PICK:
307       return true;
308     case ANIMFRAME:
309     case APPLETREADY:
310     case ATOMMOVED:
311     case CLICK:
312     case DRAGDROP:
313     case ERROR:
314     case EVAL:
315     case HOVER:
316     case IMAGE:
317     case LOADSTRUCT:
318     case MINIMIZATION:
319     case SERVICE:
320     case RESIZE:
321     case SCRIPT:
322     case SYNC:
323     case STRUCTUREMODIFIED:
324       break;
325     }
326     return false;
327   }
328 
329   @Override
330   @SuppressWarnings("incomplete-switch")
notifyCallback(CBK type, Object[] data)331   public void notifyCallback(CBK type, Object[] data) {
332     String strInfo = (data == null || data[1] == null ? null : data[1]
333         .toString());
334     switch (type) {
335     case ECHO:
336       sendConsoleEcho(strInfo);
337       break;
338     case MEASURE:
339       String mystatus = (String) data[3];
340       if (mystatus.indexOf("Picked") >= 0 || mystatus.indexOf("Sequence") >= 0) // picking mode
341         sendConsoleMessage(strInfo);
342       else if (mystatus.indexOf("Completed") >= 0)
343         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
344             strInfo.length() - 1));
345       break;
346     case MESSAGE:
347       sendConsoleMessage(data == null ? null : strInfo);
348       break;
349     case PICK:
350       sendConsoleMessage(strInfo);
351       break;
352     }
353   }
354 
355   @Override
getText()356   public String getText() {
357     return output.getText();
358   }
359 
360   @Override
sendConsoleEcho(String strEcho)361   public void sendConsoleEcho(String strEcho) {
362     if (strEcho == null) {
363       // null here means new language
364       updateLabels();
365       outputMsg(null);
366       strEcho = defaultMessage;
367     } else if (strEcho.equals("\0")) {
368       /**
369        * @j2sNative
370        *
371        * Clazz.Console.clear();
372        */
373       {}
374       strEcho = null;
375     }
376     outputMsg(strEcho);
377   }
378 
outputMsg(String message)379   private void outputMsg(String message) {
380     int n = (message == null ? -1 : message.length());
381     switch (n) {
382     case -1:
383       output.setText("");
384       return;
385     default:
386       if (message.charAt(n - 1) == '\n')
387         break;
388       //$FALL-THROUGH$
389     case 0:
390       message += "\n";
391     }
392     output.append(message);
393   }
394 
clearContent(String text)395   protected void clearContent(String text) {
396     output.setText(text);
397   }
398 
399   @Override
sendConsoleMessage(String strInfo)400   public void sendConsoleMessage(String strInfo) {
401     // null here indicates "clear console"
402     if (strInfo != null && output.getText().startsWith(defaultMessage))
403       outputMsg(null);
404     outputMsg(strInfo);
405   }
406 
407   @Override
setCallbackFunction(String callbackType, String callbackFunction)408   public void setCallbackFunction(String callbackType, String callbackFunction) {
409     // application-dependent option
410   }
411 
412   @Override
zap()413   public void zap() {
414   }
415 
416   // key listener actions
417 
recallCommand(boolean up)418   protected void recallCommand(boolean up) {
419     String cmd = vwr.getSetHistory(up ? -1 : 1);
420     if (cmd != null)
421       input.setText(PT.escUnicode(cmd));
422   }
423 
424   /**
425    *
426    * @param kcode
427    * @param kid
428    * @param isControlDown
429    * @return  1 = consume; 2 = super.process; 3 = both
430    */
processKey(int kcode, int kid, boolean isControlDown)431   protected int processKey(int kcode, int kid, boolean isControlDown) {
432     int mode = 0;
433     switch (kid) {
434     case KeyEvent.KEY_PRESSED:
435       switch (kcode) {
436       case KeyEvent.VK_TAB:
437         String s = input.getText();
438         if (s.endsWith("\n") || s.endsWith("\t"))
439           return 0;
440         mode = 1;
441         if (input.getCaretPosition() == s.length()) {
442           String cmd = completeCommand(s);
443           if (cmd != null)
444             input.setText(PT.escUnicode(cmd).replace('\t',' '));
445           nTab++;
446           return mode;
447         }
448         break;
449       case KeyEvent.VK_ESCAPE:
450         mode = 1;
451         input.setText("");
452         break;
453       }
454       nTab = 0;
455       if (kcode == KeyEvent.VK_ENTER && !isControlDown) {
456         execute(null);
457         return mode;
458       }
459       if (kcode == KeyEvent.VK_UP || kcode == KeyEvent.VK_DOWN) {
460         recallCommand(kcode == KeyEvent.VK_UP);
461         return mode;
462       }
463       break;
464     case KeyEvent.KEY_RELEASED:
465       if (kcode == KeyEvent.VK_ENTER && !isControlDown)
466         return mode;
467       break;
468     }
469     return mode | 2;
470   }
471 
472   /**
473    * separate a command line into three sections:
474    *
475    * prefix....;cmd ........ token
476    *
477    * where token can be a just-finished single or double quote or
478    * a string of characters
479    *
480    * @param cmd
481    * @return String[] {prefix, cmd..... token}
482    */
splitCommandLine(String cmd)483   private static String[] splitCommandLine(String cmd) {
484     String[] sout = new String[4];
485     boolean isEscaped1 = false;
486     boolean isEscaped2 = false;
487     boolean isEscaped = false;
488     if (cmd.length() == 0)
489       return null;
490     int ptQ = -1;
491     int ptCmd = 0;
492     int ptToken = 0;
493     int nBrace = 0;
494     char ch;
495     for (int i = 0; i < cmd.length(); i++) {
496       switch(ch = cmd.charAt(i)) {
497       case '"':
498         if (!isEscaped && !isEscaped1) {
499           isEscaped2 = !isEscaped2;
500           if (isEscaped2)
501             ptQ = ptToken = i;
502         }
503         break;
504       case '\'':
505         if (!isEscaped && !isEscaped2) {
506           isEscaped1 = !isEscaped1;
507           if (isEscaped1)
508             ptQ = ptToken = i;
509         }
510         break;
511       case '\\':
512         isEscaped = !isEscaped;
513         continue;
514       case ' ':
515         if (!isEscaped && !isEscaped1 && !isEscaped2) {
516           ptToken = i + 1;
517           ptQ = -1;
518         }
519         break;
520       case ';':
521         if (!isEscaped1 && !isEscaped2) {
522           ptCmd = ptToken = i + 1;
523           ptQ = -1;
524           nBrace = 0;
525         }
526         break;
527       case '{':
528       case '}':
529         if (!isEscaped1 && !isEscaped2) {
530           nBrace += (ch == '{' ? 1 : -1);
531           ptToken = i + 1;
532           ptQ = -1;
533         }
534         break;
535       default:
536         if (!isEscaped1 && !isEscaped2)
537           ptQ = -1;
538       }
539       isEscaped = false;
540      }
541     sout[0] = cmd.substring(0, ptCmd);
542     sout[1] = (ptToken == ptCmd ? cmd.substring(ptCmd) : cmd.substring(ptCmd, (ptToken > ptQ ? ptToken : ptQ)));
543     sout[2] = (ptToken == ptCmd ? null : cmd.substring(ptToken));
544     sout[3] = (nBrace > 0 ? "{" : null);
545     return sout;
546   }
547 
548 
549 }
550