/* * Copyright (C) 2007, 2009, 2010, 2011 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2007, 2008 Quadduc <quadduc@gmail.com> * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.subframes; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Image; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.List; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.GroupLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.GroupLayout.Alignment; import javax.swing.border.EmptyBorder; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.lateralgm.components.ColorSelect; import org.lateralgm.components.CodeTextArea; import org.lateralgm.components.MarkerCache; import org.lateralgm.components.ResourceMenu; import org.lateralgm.components.impl.IndexButtonGroup; import org.lateralgm.components.impl.TextAreaFocusTraversalPolicy; import org.lateralgm.components.mdi.RevertableMDIFrame; import org.lateralgm.main.LGM; import org.lateralgm.main.Prefs; import org.lateralgm.main.Util; import org.lateralgm.messages.Messages; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.Resource; import org.lateralgm.resources.ResourceReference; import org.lateralgm.resources.library.LibAction; import org.lateralgm.resources.library.LibArgument; import org.lateralgm.resources.sub.Action; import org.lateralgm.resources.sub.Argument; public class ActionFrame extends RevertableMDIFrame implements ActionListener { private static final long serialVersionUID = 1L; private IndexButtonGroup applies; protected ResourceMenu<GmObject> appliesObject; private JPanel appliesPanel; private Action act; private ArgumentComponent argComp[]; private JCheckBox relativeBox; private JCheckBox notBox; private JButton save; private JButton discard; public JToolBar tool; public CodeTextArea code; public JPanel status; public ActionFrame(Action a) { this(a,a.getLibAction()); } //Must be delegated through ActionFrame(Action) private ActionFrame(Action a, LibAction la) { super(la.description,false); setTitle(la.name.replace("_"," ")); if (la.parent == null) setTitle(Messages.getString("Action.UNKNOWN")); //$NON-NLS-1$ if (la.actImage != null) setFrameIcon(new ImageIcon(la.actImage.getScaledInstance(16,16,Image.SCALE_SMOOTH))); String s; ResourceReference<GmObject> at = a.getAppliesTo(); if (at == GmObject.OBJECT_SELF) s = Messages.getString("ActionFrame.SELF"); //$NON-NLS-1$ else s = Messages.getString("ActionFrame.OTHER"); //$NON-NLS-1$ appliesObject = new ResourceMenu<GmObject>(GmObject.class,s,false,100); appliesObject.setEnabled(GmObject.refAsInt(at) >= 0); appliesObject.setOpaque(false); appliesObject.setSelected(at); act = a; appliesPanel = new JPanel(); appliesPanel.setOpaque(false); appliesPanel.setLayout(new GridBagLayout()); GridBagConstraints gbc; applies = new IndexButtonGroup(3,true,false); JRadioButton button = new JRadioButton(Messages.getString("ActionFrame.SELF")); //$NON-NLS-1$ button.setOpaque(false); applies.add(button,-1); gbc = new GridBagConstraints(); gbc.gridy = 0; gbc.fill = GridBagConstraints.HORIZONTAL; appliesPanel.add(button,gbc); button = new JRadioButton(Messages.getString("ActionFrame.OTHER")); //$NON-NLS-1$ button.setOpaque(false); applies.add(button,-2); gbc = new GridBagConstraints(); gbc.gridy = 0; gbc.fill = GridBagConstraints.HORIZONTAL; appliesPanel.add(button,gbc); button = new JRadioButton(Messages.getString("ActionFrame.OBJECT")); //$NON-NLS-1$ button.setHorizontalAlignment(JRadioButton.LEFT); button.setOpaque(false); button.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { boolean sel = ((JRadioButton) e.getSource()).isSelected(); appliesObject.setEnabled(sel); } }); applies.add(button,0); gbc = new GridBagConstraints(); gbc.gridy = 1; gbc.fill = GridBagConstraints.HORIZONTAL; appliesPanel.add(button,gbc); gbc = new GridBagConstraints(); gbc.gridy = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; gbc.insets = new Insets(0,2,0,6); appliesPanel.add(appliesObject,gbc); applies.setValue(Math.min(GmObject.refAsInt(at),0)); if (la.interfaceKind == LibAction.INTERFACE_CODE) { setClosable(true); setMaximizable(true); setResizable(true); setIconifiable(true); tool = new JToolBar(); tool.setFloatable(false); tool.setAlignmentX(0); save = new JButton(LGM.getIconForKey("ActionFrame.SAVE")); //$NON-NLS-1$ save.addActionListener(this); // add(save); tool.add(save); tool.addSeparator(); code = new CodeTextArea(a.getArguments().get(0).getVal(),MarkerCache.getMarker("gml")); code.addEditorButtons(tool); if (Prefs.enableDragAndDrop) { tool.addSeparator(); tool.add(new JLabel(Messages.getString("ActionFrame.APPLIES"))); //$NON-NLS-1$ tool.add(appliesPanel); } status = new JPanel(new FlowLayout()); status.setLayout(new BoxLayout(status,BoxLayout.X_AXIS)); status.setMaximumSize(new Dimension(Integer.MAX_VALUE,11)); final JLabel caretPos = new JLabel(" INS | UTF-8 | " + (code.getCaretLine() + 1) + " : " + (code.getCaretColumn() + 1)); status.add(caretPos); code.addCaretListener(new CaretListener() { public void caretUpdate(CaretEvent e) { caretPos.setText(" INS | UTF-8 | " + (code.getCaretLine() + 1) + ":" + (code.getCaretColumn() + 1)); } }); add(tool,BorderLayout.NORTH); add(code,BorderLayout.CENTER); add(status,BorderLayout.SOUTH); setFocusTraversalPolicy(new TextAreaFocusTraversalPolicy(code.text)); appliesPanel.setLayout(new BoxLayout(appliesPanel,BoxLayout.LINE_AXIS)); pack(); if (!Prefs.enableDragAndDrop) { setSize(new Dimension(this.getWidth() + 300,this.getHeight() + 100)); } repaint(); } else { makeArgumentPane(a,la); pack(); repaint(); } SubframeInformer.fireSubframeAppear(this,false); } private void makeArgumentPane(Action a, LibAction la) { setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS)); JLabel lab; JPanel pane; pane = new JPanel(); pane.setBorder(new EmptyBorder(6,6,0,6)); pane.setLayout(new BorderLayout()); add(pane); if (la.actImage != null) { lab = new JLabel(new ImageIcon(la.actImage)); lab.setBorder(new EmptyBorder(16,16,16,20)); pane.add(lab,BorderLayout.LINE_START); } String s = Messages.getString("ActionFrame.APPLIES"); //$NON-NLS-1$ appliesPanel.setBorder(BorderFactory.createTitledBorder(s)); pane.add(appliesPanel); if (!la.canApplyTo) appliesPanel.setVisible(false); List<Argument> args = a.getArguments(); argComp = new ArgumentComponent[args.size()]; if (args.size() > 0) { pane = new JPanel(); pane.setBorder(BorderFactory.createCompoundBorder(new EmptyBorder(6,8,0,8), BorderFactory.createTitledBorder(""))); GroupLayout kvLayout = new GroupLayout(pane); GroupLayout.SequentialGroup hGroup, vGroup; GroupLayout.ParallelGroup keyGroup, valueGroup; hGroup = kvLayout.createSequentialGroup(); vGroup = kvLayout.createSequentialGroup(); keyGroup = kvLayout.createParallelGroup(Alignment.TRAILING); valueGroup = kvLayout.createParallelGroup(); hGroup.addGap(4); hGroup.addGroup(keyGroup); hGroup.addGap(6); hGroup.addGroup(valueGroup); hGroup.addGap(4); kvLayout.setHorizontalGroup(hGroup); kvLayout.setVerticalGroup(vGroup); pane.setLayout(kvLayout); add(pane); vGroup.addGap(4); for (int n = 0; n < args.size(); n++) { argComp[n] = new ArgumentComponent(args.get(n),a.getLibAction().libArguments[n]); if (la.parent == null) lab = new JLabel(Messages.format("ActionFrame.UNKNOWN",n)); //$NON-NLS-1$ else { LibArgument larg = la.libArguments[n]; lab = new JLabel(larg.caption); } Alignment al; if (n == 0 && act.getLibAction().interfaceKind == LibAction.INTERFACE_ARROWS) { argComp[n].setEditor(new ArrowsEditor(argComp[n].getArgument().getVal())); al = Alignment.CENTER; } else { Component c = argComp[n].getEditor(); c.setMaximumSize(new Dimension(240,20)); c.setPreferredSize(new Dimension(200,20)); c.setMinimumSize(new Dimension(160,20)); al = Alignment.BASELINE; } keyGroup.addComponent(lab); valueGroup.addComponent(argComp[n].getEditor()); if (n > 0) vGroup.addGap(6); GroupLayout.ParallelGroup argGroup = kvLayout.createParallelGroup(al); argGroup.addComponent(lab).addComponent(argComp[n].getEditor()); vGroup.addGroup(argGroup); } vGroup.addGap(4); } pane = new JPanel(); pane.setLayout(new FlowLayout(FlowLayout.TRAILING)); add(pane); if (la.allowRelative) { relativeBox = new JCheckBox(Messages.getString("ActionFrame.RELATIVE")); //$NON-NLS-1$ relativeBox.setSelected(act.isRelative()); pane.add(relativeBox); } if (la.question) { notBox = new JCheckBox(Messages.getString("ActionFrame.NOT")); //$NON-NLS-1$ notBox.setSelected(act.isNot()); pane.add(notBox); } pane = new JPanel(); pane.setLayout(new GridLayout(1,2,8,0)); pane.setBorder(new EmptyBorder(0,8,8,8)); add(pane); s = Messages.getString("ActionFrame.SAVE"); //$NON-NLS-1$ save = new JButton(s,LGM.getIconForKey("ActionFrame.SAVE")); //$NON-NLS-1$ save.addActionListener(this); pane.add(save); s = Messages.getString("ActionFrame.DISCARD"); //$NON-NLS-1$ discard = new JButton(s,LGM.getIconForKey("ActionFrame.DISCARD")); //$NON-NLS-1$ discard.addActionListener(this); pane.add(discard); } public ResourceReference<GmObject> getApplies() { if (applies.getValue() >= 0) { ResourceReference<GmObject> sel = appliesObject.getSelected(); if (sel != null) return sel; return act.getAppliesTo(); } if (applies.getValue() == -1) return GmObject.OBJECT_SELF; if (applies.getValue() == -2) return GmObject.OBJECT_OTHER; return null; } public void actionPerformed(ActionEvent e) { if (e.getSource() == discard) { for (ArgumentComponent a : argComp) a.discard(); close(); } else if (e.getSource() == save) { //commit changes because our resourceChanged() method does not updateResource(true); close(); } } public void commitChanges() { act.setAppliesTo(getApplies()); if (relativeBox != null) act.setRelative(relativeBox.isSelected()); if (notBox != null) act.setNot(notBox.isSelected()); switch (act.getLibAction().interfaceKind) { case LibAction.INTERFACE_CODE: act.getArguments().get(0).setVal(code.getTextCompat()); break; default: for (ArgumentComponent a : argComp) a.commit(); } } public class ArrowsEditor extends JPanel { private static final long serialVersionUID = 1L; private JToggleButton[] arrows; private final Dimension btnSize = new Dimension(32,32); private final Dimension panelSize = new Dimension(96,96); public ArrowsEditor(String val) { setLayout(new GridLayout(3,3)); arrows = new JToggleButton[9]; String location = "org/lateralgm/resources/library/default/arrows.png"; URL url = LGM.class.getClassLoader().getResource(location); BufferedImage icons; try { icons = ImageIO.read(url); } catch (IOException e) { icons = new BufferedImage(72,72,BufferedImage.TYPE_INT_ARGB); } for (int i = 0; i < 9; i++) { arrows[i] = new JToggleButton(); arrows[i].setIcon(new ImageIcon(icons.getSubimage(24 * (i % 3),24 * (i / 3),24,24))); arrows[i].setMinimumSize(btnSize); arrows[i].setPreferredSize(btnSize); int p = (2 - (i / 3)) * 3 + i % 3; if (val.length() > p) arrows[i].setSelected(val.charAt(p) == '1'); add(arrows[i]); } setMaximumSize(panelSize); setPreferredSize(panelSize); } public String getStringValue() { char[] res = new char[9]; for (int i = 0; i < 9; i++) res[i] = arrows[(2 - (i / 3)) * 3 + i % 3].isSelected() ? '1' : '0'; return new String(res); } } private class ArgumentComponent { private Argument arg; private Component editor; public ArgumentComponent(Argument arg, LibArgument libArg) { this.arg = arg; editor = makeEditor(libArg); discard(); } public Argument getArgument() { return arg; } @SuppressWarnings({ "unchecked","rawtypes" }) private JComponent makeEditor(LibArgument la) { switch (arg.kind) { case Argument.ARG_BOOLEAN: final String[] sab = { "false","true" }; return new JComboBox(sab); case Argument.ARG_MENU: if (la == null) return new JTextField(arg.getVal()); final String[] sam = la.menu.split("\\|"); //$NON-NLS-1$ return new JComboBox(sam); case Argument.ARG_COLOR: return new ColorSelect(Util.convertGmColor(Integer.parseInt(arg.getVal()))); case Argument.ARG_SPRITE: case Argument.ARG_SOUND: case Argument.ARG_BACKGROUND: case Argument.ARG_PATH: case Argument.ARG_SCRIPT: case Argument.ARG_GMOBJECT: case Argument.ARG_ROOM: case Argument.ARG_FONT: case Argument.ARG_TIMELINE: Class<? extends Resource<?,?>> rk = Argument.getResourceKind(arg.kind); String none = Messages.format("ArgumentComponent.NO_SELECTION",Resource.kindNames.get(rk)); //$NON-NLS-1$ return new ResourceMenu(rk,none,120); default: return new JTextField(arg.getVal()); } } /** * Gets a Component editor for this Argument. Defaults to raw JTextField. * @param la - The corresponding LibArgument, used for Menus. * May be null, but then a menu will default to JTextField. * @return One of ColorSelect, JComboBox, ResourceMenu, or JTextField */ public Component getEditor() { return editor; } public void setEditor(Component editor) { this.editor = editor; } /** Commits any changes in the Component editor to update this Argument. */ public void commit() { if (editor instanceof JTextField) { arg.setVal(((JTextField) editor).getText()); return; } if (editor instanceof JComboBox) { arg.setVal(Integer.toString(((JComboBox<?>) editor).getSelectedIndex())); return; } if (editor instanceof ColorSelect) { arg.setVal(Integer.toString(Util.getGmColor(((ColorSelect) editor).getSelectedColor()))); } if (editor instanceof ArrowsEditor) { arg.setVal(((ArrowsEditor) editor).getStringValue()); } if (editor instanceof ResourceMenu<?>) { arg.setRes(((ResourceMenu<?>) editor).getSelected()); return; } } @SuppressWarnings({ "rawtypes","unchecked" }) public void discard() { if (editor instanceof JTextField) { ((JTextField) editor).setText(arg.getVal()); } else if (editor instanceof JComboBox) { ((JComboBox) editor).setSelectedIndex(Integer.parseInt(arg.getVal())); } else if (editor instanceof ColorSelect) { Color c = Util.convertGmColor(Integer.parseInt(arg.getVal())); ((ColorSelect) editor).setSelectedColor(c); } else if (editor instanceof ResourceMenu) { ((ResourceMenu) editor).setSelected(arg.getRes()); } } } public Action getAction() { return act; } @Override public String getConfirmationName() { return getTitle(); } //updatable only, no revert @Override public boolean resourceChanged() { return act.getLibAction().interfaceKind == LibAction.INTERFACE_CODE && (code.isChanged() || !act.getAppliesTo().equals(getApplies())); } @Override public void revertResource() { // updatable only, no revert } @Override public void updateResource(boolean commit) { // NOTE: Ignore commit parameter, this is simply a flag to let us know if // resourceChanged() was called recently as some resources commit before // checking changes and we want to avoid resources committing twice. commitChanges(); } @Override public void setResourceChanged() { // TODO: Discussion should be held about closing associated windows. } public void focusArgumentComponent(int id) { ArgumentComponent comp = argComp[id]; if (comp != null) { comp.editor.requestFocus(); } } }