/* * Copyright (C) 2007 Clam <clamisgood@gmail.com> * Copyright (C) 2011 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2013-2014 Robert B. Colton * * This file is part of LateralGM. * * LateralGM 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. * * LateralGM 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 (COPYING) 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.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.ExceptionListener; import java.util.HashMap; import java.util.Map; import javax.swing.JButton; import javax.swing.JLabel; import org.lateralgm.components.impl.ResNode; import org.lateralgm.components.mdi.RevertableMDIFrame; import org.lateralgm.main.LGM; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Background; import org.lateralgm.resources.Constants; import org.lateralgm.resources.Extension; import org.lateralgm.resources.ExtensionPackages; import org.lateralgm.resources.Font; import org.lateralgm.resources.GameInformation; import org.lateralgm.resources.GameSettings; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.Include; import org.lateralgm.resources.Path; import org.lateralgm.resources.Resource; import org.lateralgm.resources.Room; import org.lateralgm.resources.Script; import org.lateralgm.resources.Shader; import org.lateralgm.resources.Sound; import org.lateralgm.resources.Sprite; import org.lateralgm.resources.Timeline; import org.lateralgm.ui.swing.propertylink.PropertyLinkFactory; /** Provides common functionality and structure to Resource editing frames */ public abstract class ResourceFrame<R extends Resource<R,P>, P extends Enum<P>> extends RevertableMDIFrame implements ActionListener,ExceptionListener { private static final long serialVersionUID = 1L; /** Automatically set up to save and close the frame */ public final JButton save = new JButton(); /** The resource this frame is editing (feel free to change it as you wish) */ public R res; /** Backup of res as it was before changes were made */ public R resOriginal; /** Whether changes were made and reported by the PropertyUpdateListener **/ public boolean resChanged; /** The ResNode this frame is linked to */ public final ResNode node; protected ResourceFrameListener frameListener; protected final PropertyLinkFactory<P> plf; // Static Factory methods // public static Map<Class<?>,ResourceFrameFactory> factories = new HashMap<Class<?>,ResourceFrameFactory>(); public static interface ResourceFrameFactory { public ResourceFrame<?,?> makeFrame(Resource<?,?> r, ResNode node); } private static class DefaultResourceFrameFactory implements ResourceFrameFactory { Class<?> kind; DefaultResourceFrameFactory(Class<?> kind) { this.kind = kind; } public ResourceFrame<?,?> makeFrame(Resource<?,?> r, ResNode node) { if (kind == Sprite.class) return new SpriteFrame((Sprite) r,node); else if (kind == Sound.class) return new SoundFrame((Sound) r,node); else if (kind == Background.class) return new BackgroundFrame((Background) r,node); else if (kind == Path.class) return new PathFrame((Path) r,node); else if (kind == Script.class) return new ScriptFrame((Script) r,node); else if (kind == Shader.class) return new ShaderFrame((Shader) r,node); else if (kind == Font.class) return new FontFrame((Font) r,node); else if (kind == Timeline.class) return new TimelineFrame((Timeline) r,node); else if (kind == GmObject.class) return new GmObjectFrame((GmObject) r,node); else if (kind == Room.class) return new RoomFrame((Room) r,node); else if (kind == Include.class) return new IncludeFrame((Include) r,node); else if (kind == Extension.class) return new ExtensionFrame((Extension) r,node); else if (kind == Constants.class) { LGM.showConstantsFrame(LGM.currentFile.defaultConstants); return null; } else if (kind == GameInformation.class) return LGM.getGameInfo(); else if (kind == GameSettings.class) { LGM.showGameSettings(LGM.getSelectedConfig()); return null; } else if (kind == ExtensionPackages.class) return LGM.getExtensionPackages(); else return null; } } static { for (Class<?> k : Resource.kinds) factories.put(k,new DefaultResourceFrameFactory(k)); } /** * Note for inheriting classes. Be sure to call this parent instantiation for proper setup. * The res and node parameters are only needed in the instantiation to assign globals; * That is, once you call this, they will immediately gain global scope and may be treated thusly. */ public ResourceFrame(R res, ResNode node) { this(res,node,res.getName(),true); } public ResourceFrame(R res, ResNode node, String title, boolean functional) { this(res,node,title,functional,functional,functional,functional); } public ResourceFrame(R res, ResNode node, String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable) { super(title,resizable,closable,maximizable,iconifiable); plf = new PropertyLinkFactory<P>(res.properties,this); this.res = res; this.node = node; resOriginal = res.clone(); setFrameIcon(ResNode.ICON.get(res.getClass())); save.setToolTipText(Messages.getString("ResourceFrame.SAVE")); //$NON-NLS-1$ save.setIcon(LGM.getIconForKey("ResourceFrame.SAVE")); //$NON-NLS-1$ save.addActionListener(this); } public String getConfirmationName() { return res.getName(); } public boolean resourceChanged() { commitChanges(); //NOTE: Any children that override this should call this. if (frameListener != null && frameListener.resourceChanged()) return true; if (!areResourceFieldsEqual()) return true; return !res.equals(resOriginal); } /** Override to check additional fields other than the Resource<> defaults. */ @SuppressWarnings("static-method") protected boolean areResourceFieldsEqual() { return true; } public void setFrameListener(ResourceFrameListener listener) { frameListener = listener; } //TODO: There is a fundamental flaw in the way this function is utilized. //Checking for changes causes the changes to be committed, and then if the user //does choose to save this method will also commit changes a second time. public void updateResource(boolean commit) { if (frameListener != null) frameListener.updateResource(commit); if (commit) { commitChanges(); } resOriginal = res.clone(); } @Override public void setResourceChanged() { if (frameListener != null) frameListener.setResourceChanged(); res.changed = true; } public void revertResource() { if (frameListener != null) frameListener.revertResource(); resOriginal.updateReference(); } public abstract void commitChanges(); public static void addGap(Container c, int w, int h) { JLabel l = new JLabel(); l.setPreferredSize(new Dimension(w,h)); c.add(l); } public void doDefaultSaveAction() { if (resourceChanged()) { setResourceChanged(); updateResource(false); } close(); } public void actionPerformed(ActionEvent e) { if (e.getSource() == save) { this.doDefaultSaveAction(); } } public void exceptionThrown(Exception e) { e.printStackTrace(); } public void dispose() { if (frameListener != null) frameListener.dispose(); super.dispose(); if (node != null) node.frame = null; // allows a new frame to open save.removeActionListener(this); removeAll(); plf.removeAllLinks(); } @Override public void setVisible(boolean visible) { if (frameListener != null) frameListener.setVisible(visible);; super.setVisible(visible); } public abstract interface ResourceFrameListener { public abstract void dispose(); public abstract void setVisible(boolean visible); public abstract void updateResource(boolean commit); public abstract void revertResource(); public abstract boolean resourceChanged(); public abstract void setResourceChanged(); } }