/**
* @file ShaderFrame.java
* @brief Class implementing the shader frame and a tabbed CodeTextArea
*
* @section License
*
* Copyright (C) 2013-2014 Robert B. Colton
* This file is a part of the LateralGM IDE.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
**/
package org.lateralgm.subframes;
import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.print.PrinterException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.InternalFrameEvent;
import org.lateralgm.components.CodeTextArea;
import org.lateralgm.components.MarkerCache;
import org.lateralgm.components.impl.ResNode;
import org.lateralgm.components.impl.TextAreaFocusTraversalPolicy;
import org.lateralgm.file.FileChangeMonitor;
import org.lateralgm.file.FileChangeMonitor.FileUpdateEvent;
import org.lateralgm.joshedit.Code;
import org.lateralgm.joshedit.DefaultTokenMarker;
import org.lateralgm.joshedit.JoshText.LineChangeListener;
import org.lateralgm.joshedit.lexers.GLESTokenMarker;
import org.lateralgm.joshedit.lexers.GLSLTokenMarker;
import org.lateralgm.joshedit.lexers.HLSLTokenMarker;
import org.lateralgm.main.LGM;
import org.lateralgm.main.Prefs;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.Shader;
import org.lateralgm.resources.Shader.PShader;
import org.lateralgm.ui.swing.util.SwingExecutor;
public class ShaderFrame extends InstantiableResourceFrame<Shader,PShader>
{
private static final long serialVersionUID = 1L;
public JToolBar tool;
public JTabbedPane editors;
public CodeTextArea vcode;
public CodeTextArea fcode;
public JButton edit;
public JPanel status;
public JCheckBox precompileCB;
public JComboBox<String> typeCombo;
public String currentLang = "";
private ShaderEditor fragmentEditor;
private ShaderEditor vertexEditor;
public ShaderFrame(Shader res, ResNode node)
{
super(res,node);
setSize(700,430);
setLayout(new BorderLayout());
vcode = new CodeTextArea((String) res.get(PShader.VERTEX),MarkerCache.getMarker("glsles"));
fcode = new CodeTextArea((String) res.get(PShader.FRAGMENT),MarkerCache.getMarker("glsles"));
editors = new JTabbedPane();
editors.addTab("Vertex",vcode);
editors.addTab("Fragment",fcode);
add(editors,BorderLayout.CENTER);
// Setup the toolbar
tool = new JToolBar();
tool.setFloatable(false);
tool.setAlignmentX(0);
add(tool,BorderLayout.NORTH);
tool.add(save);
tool.addSeparator();
if (Prefs.useExternalScriptEditor) {
vcode.setEnabled(false);
edit = new JButton(LGM.getIconForKey("ShaderFrame.EDIT")); //$NON-NLS-1$
edit.setToolTipText(Messages.getString("ShaderFrame.EDIT"));
edit.addActionListener(this);
tool.add(edit);
tool.addSeparator();
}
this.addEditorButtons(tool);
tool.addSeparator();
tool.add(new JLabel(Messages.getString("ShaderFrame.TYPE")));
String[] typeOptions = { "GLSLES","GLSL","HLSL9","HLSL11" };
typeCombo = new JComboBox<String>(typeOptions);
typeCombo.setMaximumSize(typeCombo.getPreferredSize());
typeCombo.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent arg0)
{
updateLexer();
}
});
typeCombo.setSelectedItem(res.getType());
tool.add(typeCombo);
precompileCB = new JCheckBox(Messages.getString("ShaderFrame.PRECOMPILE"));
precompileCB.setSelected(res.getPrecompile());
precompileCB.setOpaque(false);
tool.addSeparator();
tool.add(precompileCB);
tool.addSeparator();
name.setColumns(13);
name.setMaximumSize(name.getPreferredSize());
tool.add(new JLabel(Messages.getString("ShaderFrame.NAME"))); //$NON-NLS-1$
tool.add(name);
status = new JPanel(new FlowLayout());
BoxLayout layout = new BoxLayout(status,BoxLayout.X_AXIS);
status.setLayout(layout);
status.setMaximumSize(new Dimension(Integer.MAX_VALUE,11));
final JLabel caretPos = new JLabel(" INS | UTF-8 | " + (vcode.getCaretLine() + 1) + " : "
+ (vcode.getCaretColumn() + 1));
status.add(caretPos);
vcode.addCaretListener(new CaretListener()
{
public void caretUpdate(CaretEvent e)
{
caretPos.setText(" INS | UTF-8 | " + (vcode.getCaretLine() + 1) + " : "
+ (vcode.getCaretColumn() + 1));
}
});
fcode.addCaretListener(new CaretListener()
{
public void caretUpdate(CaretEvent e)
{
caretPos.setText(" INS | UTF-8 | " + (fcode.getCaretLine() + 1) + " : "
+ (fcode.getCaretColumn() + 1));
}
});
add(status,BorderLayout.SOUTH);
setFocusTraversalPolicy(new TextAreaFocusTraversalPolicy(vcode.text));
updateLexer();
}
private void updateLexer()
{
//TODO: This should be moved into the base CodeTextArea, as a feature of JoshEdit
String val = typeCombo.getSelectedItem().toString();
if (val.equals(currentLang))
{
return;
}
DefaultTokenMarker marker = null;
if (val.equals("GLSLES"))
{
marker = new GLESTokenMarker();
}
else if (val.equals("GLSL"))
{
marker = new GLSLTokenMarker();
}
else if (val.equals("HLSL9"))
{
marker = new HLSLTokenMarker();
}
else if (val.equals("HLSL11"))
{
marker = new HLSLTokenMarker();
}
else
{
}
//TODO: Both of these calls will utilize the same lexer, but they both
//will recompose the list of completions. Should possibly add an abstract
//GetCompletions() to the DefaultTokenMarker class, so that all code editors
//can utilize the same completions list to save memory.
vcode.setTokenMarker(marker);
fcode.setTokenMarker(marker);
currentLang = val;
repaint();
}
public void commitChanges()
{
res.put(PShader.VERTEX,vcode.getTextCompat());
res.put(PShader.FRAGMENT,fcode.getTextCompat());
res.setName(name.getText());
res.put(PShader.TYPE,typeCombo.getSelectedItem());
res.put(PShader.PRECOMPILE,precompileCB.isSelected());
}
public void fireInternalFrameEvent(int id)
{
if (id == InternalFrameEvent.INTERNAL_FRAME_CLOSED)
LGM.currentFile.updateSource.removeListener(vcode);
LGM.currentFile.updateSource.removeListener(fcode);
super.fireInternalFrameEvent(id);
}
private enum EditorType { VERTEX, FRAGMENT };
private class ShaderEditor implements UpdateListener
{
public final FileChangeMonitor monitor;
private final EditorType type;
private final File f;
public ShaderEditor(EditorType type) throws IOException
{
this.type = type;
f = File.createTempFile(res.getName(),"." +
(type == EditorType.VERTEX ? "vert" : "frag"), LGM.tempDir); //$NON-NLS-1$
f.deleteOnExit();
monitor = new FileChangeMonitor(f,SwingExecutor.INSTANCE);
monitor.updateSource.addListener(this,true);
start();
}
public void start() throws IOException
{
FileWriter out = null;
try
{
out = new FileWriter(f);
out.write(type == EditorType.VERTEX ? vcode.getTextCompat() : fcode.getTextCompat());
}
finally
{
if (out != null)
{
out.close();
}
}
out.close();
if (!Prefs.useExternalScriptEditor || Prefs.externalScriptEditorCommand == null)
try
{
System.out.println(Desktop.getDesktop());
// Desktop d = Desktop.getDesktop();
// Desktop.Action.EDIT;
// Toolkit.getDefaultToolkit().createDesktopPeer(d);
Desktop.getDesktop().edit(monitor.file);
}
catch (UnsupportedOperationException e)
{
throw new UnsupportedOperationException("no internal or system script editor",e);
}
else
Runtime.getRuntime().exec(
String.format(Prefs.externalScriptEditorCommand,monitor.file.getAbsolutePath()));
}
public void stop()
{
monitor.stop();
monitor.file.delete();
}
public void updated(UpdateEvent e)
{
if (!(e instanceof FileUpdateEvent)) return;
switch (((FileUpdateEvent) e).flag)
{
case CHANGED:
StringBuffer sb = new StringBuffer(1024);
BufferedReader reader = null;
try
{
reader = new BufferedReader(new FileReader(monitor.file));
char[] chars = new char[1024];
int len = 0;
while ((len = reader.read(chars)) > -1)
sb.append(chars,0,len);
}
catch (IOException ioe)
{
LGM.showDefaultExceptionHandler(ioe);
return;
}
finally
{
if (reader != null)
{
try
{
reader.close();
}
catch (IOException ex)
{
LGM.showDefaultExceptionHandler(ex);
}
}
}
String s = sb.toString();
if (type == EditorType.VERTEX) {
res.put(PShader.VERTEX,s);
vcode.setText(s);
} else {
res.put(PShader.FRAGMENT,s);
fcode.setText(s);
}
break;
case DELETED:
if (type == EditorType.VERTEX) {
vertexEditor = null;
} else {
fragmentEditor = null;
}
}
}
}
public void dispose()
{
if (fragmentEditor != null) {
fragmentEditor.stop();
fragmentEditor = null;
}
if (vertexEditor != null) {
vertexEditor.stop();
vertexEditor = null;
}
super.dispose();
}
private JButton makeToolbarButton(String name)
{
String key = "JoshText." + name;
JButton b = new JButton(LGM.getIconForKey(key));
b.setToolTipText(Messages.getString(key));
b.setRequestFocusEnabled(false);
b.setActionCommand(key);
b.addActionListener(this);
return b;
}
public void addEditorButtons(JToolBar tb)
{
tb.add(makeToolbarButton("LOAD"));
tb.add(makeToolbarButton("SAVE"));
tb.add(makeToolbarButton("PRINT"));
tb.addSeparator();
tb.add(makeToolbarButton("CUT"));
tb.add(makeToolbarButton("COPY"));
tb.add(makeToolbarButton("PASTE"));
tb.addSeparator();
final JButton undoButton = makeToolbarButton("UNDO");
tb.add(undoButton);
final JButton redoButton = makeToolbarButton("REDO");
tb.add(redoButton);
// need to set the default state unlike the component popup
undoButton.setEnabled(vcode.text.canUndo());
redoButton.setEnabled(vcode.text.canRedo());
LineChangeListener linelistener = new LineChangeListener() {
@Override
public void linesChanged(Code code, int start, int end)
{
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run()
{
CodeTextArea selectedCode = getSelectedCode();
undoButton.setEnabled(selectedCode.text.canUndo());
redoButton.setEnabled(selectedCode.text.canRedo());
}
});
}
};
editors.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
CodeTextArea selectedCode = getSelectedCode();
if (selectedCode == null) return;
undoButton.setEnabled(selectedCode.text.canUndo());
redoButton.setEnabled(selectedCode.text.canRedo());
}
});
fcode.text.addLineChangeListener(linelistener);
vcode.text.addLineChangeListener(linelistener);
tb.addSeparator();
tb.add(makeToolbarButton("FIND"));
tb.add(makeToolbarButton("GOTO"));
}
public CodeTextArea getSelectedCode() {
int stab = editors.getSelectedIndex();
if (stab == 0)
{
return vcode;
}
else if (stab == 1)
{
return fcode;
}
// do not know which tab you have selected
return null;
}
@Override
public void actionPerformed(ActionEvent ev)
{
super.actionPerformed(ev);
if (ev.getSource() == edit)
{
try
{
int stab = editors.getSelectedIndex();
if (stab == 0)
{
if (vertexEditor == null) {
vertexEditor = new ShaderEditor(EditorType.VERTEX);
} else {
vertexEditor.start();
}
}
else if (stab == 1)
{
if (fragmentEditor == null) {
fragmentEditor = new ShaderEditor(EditorType.FRAGMENT);
} else {
fragmentEditor.start();
}
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
return;
}
String com = ev.getActionCommand();
CodeTextArea selectedCode = getSelectedCode();
if (com.equals("JoshText.LOAD"))
{
selectedCode.text.Load();
}
else if (com.equals("JoshText.SAVE"))
{
selectedCode.text.Save();
}
else if (com.equals("JoshText.PRINT"))
{
try
{
selectedCode.Print();
}
catch (PrinterException e)
{
LGM.showDefaultExceptionHandler(e);
}
}
else if (com.equals("JoshText.UNDO"))
{
selectedCode.text.Undo();
}
else if (com.equals("JoshText.REDO"))
{
selectedCode.text.Redo();
}
else if (com.equals("JoshText.CUT"))
{
selectedCode.text.Cut();
}
else if (com.equals("JoshText.COPY"))
{
selectedCode.text.Copy();
}
else if (com.equals("JoshText.PASTE"))
{
selectedCode.text.Paste();
}
else if (com.equals("JoshText.FIND"))
{
selectedCode.text.ShowFind();
}
else if (com.equals("JoshText.GOTO"))
{
selectedCode.aGoto();
}
else if (com.equals("JoshText.SELALL"))
{
selectedCode.text.SelectAll();
}
}
}