diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/meper/src |
Initial commit
Diffstat (limited to 'support/meper/src')
-rw-r--r-- | support/meper/src/EditorFilter.java | 261 | ||||
-rw-r--r-- | support/meper/src/MEPer.java | 362 | ||||
-rw-r--r-- | support/meper/src/README | 28 |
3 files changed, 651 insertions, 0 deletions
diff --git a/support/meper/src/EditorFilter.java b/support/meper/src/EditorFilter.java new file mode 100644 index 0000000000..86febab96d --- /dev/null +++ b/support/meper/src/EditorFilter.java @@ -0,0 +1,261 @@ +/* EditorFilter.java + * This file is part of source files of MEPer. + * It defines the behaviors of the editor. + * + * @author Shengjun Pan +*/ + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.text.*; +import java.util.*; +import javax.swing.undo.*; + +public class EditorFilter extends DocumentFilter { + + String[] keywords ={"beginfig","endfig","for","draw","drawarrow","endfor", + "def", "vardef", "enddef","if", "fi", "withcolor","fill", + "shifted", "scaled", "rotated", "withpen"}; + String[] datatypes = {"picture","numeric","path","pair","transform","color", + "string", "boolean", "pen"}; + + int fontSize = 14; + HashMap<String,SimpleAttributeSet> tokenToAset; + JTextPane editor; + JButton saveButton; + + public EditorFilter(JTextPane ed, JButton sb) { + + editor = ed; + saveButton = sb; + + tokenToAset = new HashMap<String,SimpleAttributeSet>(); + + SimpleAttributeSet aset= new SimpleAttributeSet(); + StyleConstants.setForeground(aset, Color.BLUE); + StyleConstants.setBold(aset, true); + StyleConstants.setFontSize(aset,fontSize); + tokenToAset.put("keyword", aset); + + aset= new SimpleAttributeSet(); + StyleConstants.setForeground(aset, Color.GREEN); + StyleConstants.setBold(aset, true); + StyleConstants.setFontSize(aset,fontSize); + tokenToAset.put("digits", aset); + + aset= new SimpleAttributeSet(); + StyleConstants.setForeground(aset, Color.BLACK); + StyleConstants.setBold(aset, false); + StyleConstants.setFontSize(aset,fontSize); + tokenToAset.put("normal", aset); + + aset= new SimpleAttributeSet(); + StyleConstants.setForeground(aset, Color.GREEN); + StyleConstants.setBold(aset, true); + StyleConstants.setFontSize(aset,fontSize); + tokenToAset.put("datatype", aset); + + aset= new SimpleAttributeSet(); + StyleConstants.setForeground(aset, Color.MAGENTA); + StyleConstants.setBold(aset, true); + StyleConstants.setFontSize(aset,fontSize); + tokenToAset.put("quoted", aset); + + //editor.addKeyListener(this); + editor.setFont(new Font(Font.MONOSPACED,Font.PLAIN,fontSize)); + + // enable undo/redo + final UndoManager undoManager = new UndoManager(); + editor.getDocument().addUndoableEditListener(undoManager); + editor.getInputMap().put(KeyStroke.getKeyStroke("ctrl Z"), "undo"); + editor.getActionMap().put("undo", new AbstractAction() { + public void actionPerformed(ActionEvent evt) { + try { undoManager.undo();} + catch (Throwable t) {Toolkit.getDefaultToolkit().beep();} + }}); + editor.getInputMap().put(KeyStroke.getKeyStroke("ctrl R"), "redo"); + editor.getActionMap().put("redo", new AbstractAction() { + public void actionPerformed(ActionEvent evt) { + try { undoManager.redo();} + catch (Throwable t) {Toolkit.getDefaultToolkit().beep();} + }}); + + } + + @Override + public void insertString(FilterBypass fb, int offs, + String str, AttributeSet a) + throws BadLocationException { + saveButton.setEnabled(true); + highlightAndInsert(fb, offs, str); + } + + @Override + public void replace(DocumentFilter.FilterBypass fb, int offs, int len, + String str, AttributeSet a) throws BadLocationException { + saveButton.setEnabled(true); + super.remove(fb, offs, len); + highlightAndInsert(fb,offs,str); + } + + @Override + public void remove(DocumentFilter.FilterBypass fb, int offs, int len) + throws BadLocationException { + saveButton.setEnabled(true); + boolean removingWhite = fb.getDocument().getText(offs, len).trim().isEmpty(); + + super.remove(fb, offs, len); + String indent = ""; + String smallerIndent =""; + try { + indent = indent(offs,false); + smallerIndent = indent(offs,true); + int newLineOffs = offs - indent.length()-1; + String maybeNewline=newLineOffs<0?"":editor.getDocument().getText(newLineOffs, 1); + if(removingWhite && indent.length()>0 && (maybeNewline.isEmpty() || maybeNewline.equals("\n"))){ + super.remove(fb,newLineOffs+1, indent.length()); + super.insertString(fb,newLineOffs+1,smallerIndent, tokenToAset.get("normal")); + } else if(offs>0) { highlightAndInsert(fb,offs,"");} + } catch(Throwable t) {} + } + + private void highlightAndInsert(FilterBypass fb, int offs, String str) + throws BadLocationException { + + // invoke automatical indentation only if a single <ENTER> is received. + // In the case of pasting multiple lines, the original indentation + // should be reserved. + if(str.length()==1 && str.charAt(0)=='\n') { + String indent = ""; + try { + indent = indent(offs,false); + super.insertString(fb,offs, "\n"+indent, tokenToAset.get("normal")); + } catch(Throwable t) {} + return; + } + + //translate + String newstr=""; + for(int i=0;i<str.length(); i++){ + char c= str.charAt(i); + switch(c) { + case '\t': + { + newstr += " "; + break; + } + default: + newstr += c; + } + } + + String prefix, suffix; + // search for partial word before + int from=offs; + if (newstr.isEmpty() || !Character.isWhitespace(newstr.charAt(0))){ + from --; + while(from >=0 ) { + char c = fb.getDocument().getText(from, 1).charAt(0); + if(Character.isWhitespace(c)){ break;} + from--; + } + from ++; + } + prefix = fb.getDocument().getText(from, offs-from); + + // search for partial word after + int to=offs; + if (newstr.isEmpty() || !Character.isWhitespace(newstr.charAt(newstr.length()-1))){ + int wholeLen = fb.getDocument().getLength(); + while(to < wholeLen) { + char c = fb.getDocument().getText(to,1).charAt(0); + if(Character.isWhitespace(c)){break;} + to++; + } + } + suffix = fb.getDocument().getText(offs,to-offs); + + // removing the partial words + offs -= prefix.length(); + super.remove(fb, offs, prefix.length()+suffix.length()); + + + String[] words = newstr.split(" ",-1); + words[0] = prefix + words[0]; + words[words.length-1] += suffix; + + + newstr = prefix + newstr + suffix; + String state="normal"; + if(offs>0){ + Color fgcolor=StyleConstants.getForeground(editor.getStyledDocument().getCharacterElement(offs-1).getAttributes()); + if (fgcolor.equals(StyleConstants.getForeground(tokenToAset.get("quoted")))) + state = "quoted"; + } + + String token=""; + char c; + newstr += '\0'; + for(int i=0;i< newstr.length();i++){ + c = newstr.charAt(i); + String newstate="normal"; + if(c=='\0') newstate = "done"; + else if(state.equals("quoted")|| state.equals("lquote")) newstate= (c=='\"')? "rquote":"quoted"; + else if(Character.isDigit(c)) newstate = "digits"; + else if(Character.isLetter(c)) newstate = "word"; + else if(c=='\"') newstate= "lquote"; + + if (!state.equals(newstate)) { + String key="normal"; + if(state.equals("digits")) key="digits"; + else if(state.equals("word") && isKeyword(token)) key="keyword"; + else if(state.equals("word") && isDatatype(token)) key="datatype"; + else if(state.equals("quoted")) key="quoted"; + + super.insertString(fb, offs, token, tokenToAset.get(key)); + offs += token.length(); + state = newstate; + token = ""; + } + token +=c; + } + + editor.setCaretPosition(offs-suffix.length()); + } + + // return the indent (as string of whitespaces) of the current line + // or closest shorter indent of previous lines if 'wantShorter' is true. + private String indent(int offs, boolean wantShorter) throws BadLocationException { + int pos=offs-1; + int pos_firstNonWhite = offs; + int firstIndentSize=-1; + while(pos>=0){ + char ch= editor.getText(pos, 1).charAt(0); + if(ch=='\n') { + int previousIndentSize = pos_firstNonWhite-pos-1; + if(!wantShorter || previousIndentSize < firstIndentSize) break; + if(firstIndentSize<0) firstIndentSize = previousIndentSize; + pos_firstNonWhite = pos; + } + if(!Character.isWhitespace(ch)) { + pos_firstNonWhite = pos; + } + pos --; + } + return editor.getText(pos+1, pos_firstNonWhite-pos-1); + } + + private boolean isKeyword(String word) { + for(int i=0;i<keywords.length;i++) + if(keywords[i].equals(word)) + return true; + return false; + } + private boolean isDatatype(String word) { + for(int i=0;i<datatypes.length;i++) + if(datatypes[i].equals(word)) + return true; + return false; + } +} diff --git a/support/meper/src/MEPer.java b/support/meper/src/MEPer.java new file mode 100644 index 0000000000..62d64945ad --- /dev/null +++ b/support/meper/src/MEPer.java @@ -0,0 +1,362 @@ +/* MEPer.java + * This file is part of source files of MEPer. + * It create the main window and starts the program. + * + * @author Shengjun Pan +*/ + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; +import javax.swing.text.*; +import java.io.*; + + +public class MEPer extends JFrame { + + static int initWidth=1000; + static int initHeight=618; + + boolean ispc; + JToolBar toolbar; + JTextPane editor; + JLabel graph; + JLabel statusLabel; + + JButton saveButton; + JButton previewButton; + JButton helpButton; + + Action saveAction; + Action previewAction; + Action helpAction; + JDialog helpWindow=null; + + EditorFilter editorFilter; + + String workingDir; + + static String helpTitle = "Metapost Editor and Previewer v1.0"; + static String dirName= "MetaPostPreview"; + static String metapost = "mpost -interaction=nonstopmode preview.mp end"; + static String convert = "convert preview.eps preview.png"; + static String ghostscript = " -dBATCH -dNOPAUSE -dQUIET"+ + " -dEPSCrop -dEPSFitPage -dGraphicsAlphaBits=4 -dTextAlphaBits=4"+ + " -sDEVICE=pngalpha -sOutputFile=preview.png preview.eps"; + + private void saveFile() { + try { + FileWriter outputStream = new FileWriter(workingDir+File.separator+"preview.mp"); + outputStream.write(editor.getText()); + outputStream.close(); + saveButton.setEnabled(false); + } catch (Throwable t){} + } + + private void consumeRuntime(Process proc) { + final BufferedReader errorConsumer = new BufferedReader(new InputStreamReader(proc.getErrorStream())); + final BufferedReader outputConsumer = new BufferedReader(new InputStreamReader(proc.getInputStream())); + Thread errorThread = new Thread() { + public void run() { + String nextline; + try { + while ( (nextline = errorConsumer.readLine()) != null); + } catch (Throwable t){} + }}; + Thread outputThread = new Thread(){ + public void run() { + String nextline; + try { + while ( (nextline = outputConsumer.readLine()) != null); + } catch (Throwable t){} + }}; + errorThread.start(); + outputThread.start(); + } + + private void createActions(){ + saveAction = new AbstractAction () { + public void actionPerformed(ActionEvent e) { + saveFile(); + statusLabel.setText("preview.mp saved in directory: "+ workingDir); + editor.requestFocus(); + }}; + previewAction = new AbstractAction(){ + public void actionPerformed(ActionEvent e) { + saveFile(); + + SwingUtilities.invokeLater(new Runnable () { + public void run() { + try { + File previewmp= new File(workingDir+File.separator+"preview.mp"); + File preview1= new File(workingDir+File.separator+"preview.1"); + File previeweps= new File(workingDir+File.separator+"preview.eps"); + File previewpng= new File(workingDir+File.separator+"preview.png"); + + (new File(workingDir+File.separator+"preview.1")).delete(); + (new File(workingDir+File.separator+"preview.eps")).delete(); + (new File(workingDir+File.separator+"preview.png")).delete(); + + File dir = new File(workingDir); + Process proc=null; + + if(previewmp.exists()) { + statusLabel.setText("Generating preview image..."); + proc = Runtime.getRuntime().exec(metapost,null,dir); + if(proc != null) { + consumeRuntime(proc); + proc.waitFor(); + proc = null; + } else { + statusLabel.setText("Error on running metapost."); + return; + } + } else { + statusLabel.setText("Can't find file: preview.mp"); + return; + } + + if(preview1.exists()) { + preview1.renameTo(previeweps); + statusLabel.setText("preview.1 renamed to preview.eps."); + } else { + statusLabel.setText("Cant' find file: preview.1"); + return; + } + + if(previeweps.exists()) { + // try convert + proc = Runtime.getRuntime().exec(convert, null,dir); + if(proc != null) { + consumeRuntime(proc); + proc.waitFor(); + proc=null; + } else + statusLabel.setText("Error on running ImageMagick." + + "Try ghostscript..."); + // try ghostscript + if(!previewpng.exists()) { + proc = Runtime.getRuntime().exec(ghostscript, null,dir); + if(proc != null) { + consumeRuntime(proc); + proc.waitFor(); + proc=null; + } else { + statusLabel.setText("Error on running ghostscript."); + return; + } + } + } else { + statusLabel.setText("Can't find file: preview.eps"); + return; + } + + if(previewpng.exists()) { + statusLabel.setText("Reloading preview image..."); + ((ImageIcon)graph.getIcon()).getImage().flush(); + ImageIcon img = new ImageIcon(workingDir+File.separator+"preview.png"); + graph.setIcon(img); + graph.repaint(); + statusLabel.setText(" preview.[mp|eps|png] saved in directory: "+ workingDir); + } else { + statusLabel.setText("Can't find file: preview.png"); + return; + } + + } catch (Throwable t) {} + editor.requestFocus(); + }}); + }}; + helpAction = new AbstractAction () { + public void actionPerformed(ActionEvent e) { + if(helpWindow==null){ + helpWindow = new JDialog(); + JTextArea infoPane = new JTextArea(); + infoPane.setEditable(false); + infoPane.setAlignmentX(Component.CENTER_ALIGNMENT); + //URL readmeURL = this.getClass().getResource("README.txt"); + + try { + String nextline; + InputStream is = this.getClass().getResourceAsStream("README.txt"); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader inputStream = new BufferedReader(isr); + nextline = inputStream.readLine(); + infoPane.getDocument().insertString(infoPane.getDocument().getLength(), nextline, null); + while(!(nextline = inputStream.readLine()).equals(null)) + infoPane.getDocument().insertString(infoPane.getDocument().getLength(), "\n"+nextline, null); + inputStream.close(); + } catch(Throwable t) {} + + Container helpPane = helpWindow.getContentPane(); + + helpPane.setLayout(new BoxLayout(helpPane,BoxLayout.Y_AXIS)); + JLabel titleLabel = new JLabel(helpTitle); + titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + helpPane.add(titleLabel); + infoPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + helpPane.add(infoPane); + JButton closeButton = new JButton(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + helpWindow.setVisible(false); + editor.requestFocus(); + }}); + closeButton.setText("Close"); + closeButton.setAlignmentX(Component.CENTER_ALIGNMENT); + helpPane.add(closeButton); + helpWindow.addWindowListener( new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + helpWindow.setVisible(false); + editor.requestFocus(); + }}); + helpWindow.pack(); + } + + int topX, topY; + topX = (MEPer.this.getWidth()-helpWindow.getWidth())/2; + topY = (MEPer.this.getHeight()-helpWindow.getHeight())/3; + Point helpLocation =MEPer.this.getLocation(); + helpLocation.translate(topX, topY); + helpWindow.setLocation(helpLocation); + helpWindow.setVisible(true); + } + }; + } + + public MEPer() { + + String osName=System.getProperty("os.name").toLowerCase(); + ispc = osName.indexOf("win")!=-1; + ghostscript = (ispc?"gsWin32":"gs") + ghostscript; + + workingDir = System.getProperty("user.dir"); + File newdir = new File(System.getProperty("user.home") + File.separator+ dirName); + + if(newdir.exists() || newdir.mkdir()) { + workingDir = newdir.getAbsolutePath(); + System.setProperty("user.dir", workingDir); + } + + // Create a toolbar + toolbar = new JToolBar(); + createActions(); + + saveButton = new JButton(saveAction); + saveButton.setText("Save"); + saveButton.setToolTipText("Ctr+S"); + toolbar.add(saveButton); + + previewButton = new JButton(previewAction); + previewButton.setText("Preview"); + previewButton.setToolTipText("Ctr+P"); + toolbar.add(previewButton); + + toolbar.add(Box.createHorizontalGlue()); + + helpButton = new JButton(helpAction); + helpButton.setText("Help"); + toolbar.add(helpButton); + + statusLabel = new JLabel(); + statusLabel.setText("Working directory: "+System.getProperty("user.dir")); + statusLabel.setFont(new Font(Font.SERIF,Font.PLAIN,12)); + + //Create a text pane for the editing area + editor = new JTextPane(); + + editorFilter = new EditorFilter(editor, saveButton); + ((AbstractDocument)editor.getDocument()).setDocumentFilter(editorFilter); + editor.getInputMap().put(KeyStroke.getKeyStroke("ctrl S"), "save"); + editor.getInputMap().put(KeyStroke.getKeyStroke("ctrl P"), "preview"); + editor.getActionMap().put("save", saveAction); + editor.getActionMap().put("preview", previewAction); +/* + */ + // load file "preview.mp" into editor + BufferedReader inputStream; + String mpName = workingDir+File.separator+"preview.mp"; + try { + boolean loadTemplate = false; + if ((new File(mpName)).exists()) + inputStream = new BufferedReader(new FileReader(mpName)); + else{ + statusLabel.setText("Can't load preview.mp; using template instead."); + loadTemplate=true; + InputStream is = this.getClass().getResourceAsStream("template.mp"); + InputStreamReader isr = new InputStreamReader(is); + inputStream = new BufferedReader(isr); + } + String nextline; + nextline = inputStream.readLine(); + editor.getDocument().insertString(editor.getDocument().getLength(), nextline, null); + while(!((nextline = inputStream.readLine())==null)) + editor.getDocument().insertString(editor.getDocument().getLength(), "\n"+nextline, null); + inputStream.close(); + if(loadTemplate) editor.setCaretPosition(editor.getDocument().getLength()-12); + } catch(Throwable t) {} + + saveButton.setEnabled(false); + + JScrollPane editorScrollPane = new JScrollPane(editor); + + graph = new JLabel(); + graph.setOpaque(true); + graph.setBackground(Color.WHITE); + ImageIcon img = new ImageIcon(workingDir+File.separator+"preview.png"); + graph.setIcon(img); + graph.setHorizontalAlignment(SwingConstants.CENTER); + graph.setVerticalAlignment(SwingConstants.CENTER); + + JScrollPane imagePane = new JScrollPane(graph); + + editorScrollPane.setPreferredSize(new Dimension(initWidth/2,initHeight)); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, + editorScrollPane, imagePane); + + this.getContentPane().add(toolbar,BorderLayout.PAGE_START); + this.getContentPane().add(splitPane, BorderLayout.CENTER); + this.getContentPane().add(statusLabel,BorderLayout.PAGE_END); + + addWindowListener( new WindowAdapter() { + @Override + public void windowOpened( WindowEvent e ){ editor.requestFocus();} + @Override + public void windowClosing(WindowEvent e) { + if( saveButton.isEnabled()) { + int response = JOptionPane.showConfirmDialog( + MEPer.this, + "Save file before exit?", + "Closing...", + JOptionPane.YES_NO_OPTION); + if (response == JOptionPane.YES_OPTION) saveFile(); + }} + }); + } + + private static void initialize() { + MEPer mainFrame = new MEPer(); + mainFrame.setTitle("MetaPost Editor and Previewer"); + + mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int topX = (int)((screenSize.getWidth()-initWidth)/2); + if(topX<0) topX=0; + int topY =(int)((screenSize.getHeight()-initHeight)/3); + if(topY<0) topY=0; + mainFrame.setPreferredSize(new Dimension(initWidth,initHeight)); + mainFrame.setLocation(topX, topY); + mainFrame.pack(); + mainFrame.setVisible(true); + } + + public static void main(String args[]) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + initialize(); + }}); +} +} diff --git a/support/meper/src/README b/support/meper/src/README new file mode 100644 index 0000000000..f0b956915f --- /dev/null +++ b/support/meper/src/README @@ -0,0 +1,28 @@ +Usage + MEPer is a java program. To start the program, use + java -jar MEper.jar + + A directory named "MetaPostPreviewer" will be created in the user's home + directory. The following files (and temporary files) will be created in this + directory: + - preview.mp, the MetaPost source file. + - preview.eps, the output of the MetaPost; + - preview.png, converted from the eps file. + + The jar file and source files can be downloaed from + http://www.cse.ucsd.edu/~s1pan/MEPer. + +System requirement + The following tools are needed to run MEPer: + - the latest release of the Sun Java SE platform + - MetaPost + - ImageMagick, or ghostscript + +License and Disclaimer + MEPer is free software under the GPL (Gnu General Public License). It is free + to use but WITHOUT ANY WARRANTY. If you modify or redistribute it, please + include the orginal author's name or a link to the MEPer web page. + +Copyright 2009, Shengjun Pan + + |