/*
* Copyright (C) 2007, 2008 Clam <clamisgood@gmail.com>
* Copyright (C) 2008, 2009 Quadduc <quadduc@gmail.com>
* Copyright (C) 2013, 2014 Robert B. Colton
*
* 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 static javax.swing.GroupLayout.DEFAULT_SIZE;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.text.NumberFormatter;
import org.lateralgm.components.EffectsFrame;
import org.lateralgm.components.EffectsFrame.EffectsFrameListener;
import org.lateralgm.components.NumberField;
import org.lateralgm.components.impl.ResNode;
import org.lateralgm.components.visual.BackgroundPreview;
import org.lateralgm.file.FileChangeMonitor;
import org.lateralgm.file.FileChangeMonitor.FileUpdateEvent;
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.main.Util;
import org.lateralgm.messages.Messages;
import org.lateralgm.resources.Background;
import org.lateralgm.resources.Background.PBackground;
import org.lateralgm.ui.swing.util.SwingExecutor;
import org.lateralgm.util.PropertyMap.PropertyUpdateEvent;
import org.lateralgm.util.PropertyMap.PropertyUpdateListener;
public class BackgroundFrame extends InstantiableResourceFrame<Background,PBackground> implements
UpdateListener, EffectsFrameListener
{
private static final long serialVersionUID = 1L;
public JButton load;
public JLabel statusLabel;
public JCheckBox transparent;
public JButton edit, zoomIn, zoomOut;
public JToggleButton zoomButton;
public JCheckBox smooth;
public JCheckBox preload;
public JCheckBox tileset;
public MouseListener mouseListener = null;
public NumberField tWidth;
public NumberField tHeight;
public NumberField hOffset;
public NumberField vOffset;
public NumberField hSep;
public NumberField vSep;
public JScrollPane previewScroll;
public BackgroundPreview preview;
public boolean imageChanged = false;
private BackgroundEditor editor;
private final BackgroundPropertyListener bpl = new BackgroundPropertyListener();
/** Zoom in, centering around a specific point, usually the mouse. */
public void zoomIn(Point point)
{
if (this.getZoom() >= 32) return;
this.setZoom(this.getZoom() * 2);
Dimension size = previewScroll.getViewport().getSize();
int newX = (int) (point.x * 2) - size.width / 2;
int newY = (int) (point.y * 2) - size.height / 2;
previewScroll.getViewport().setViewPosition(new Point(newX,newY));
previewScroll.revalidate();
previewScroll.repaint();
}
/** Zoom out, centering around a specific point, usually the mouse. */
public void zoomOut(Point point)
{
if (this.getZoom() <= 0.04) return;
this.setZoom(this.getZoom() / 2);
Dimension size = previewScroll.getViewport().getSize();
int newX = (int) (point.x / 2) - size.width / 2;
int newY = (int) (point.y / 2) - size.height / 2;
previewScroll.getViewport().setViewPosition(new Point(newX,newY));
previewScroll.revalidate();
previewScroll.repaint();
}
public void zoomIn()
{
Dimension size = previewScroll.getViewport().getViewSize();
zoomIn(new Point(size.width/2,size.height/2));
}
public void zoomOut()
{
Dimension size = previewScroll.getViewport().getViewSize();
zoomOut(new Point(size.width/2,size.height/2));
}
public BackgroundFrame(Background res, ResNode node)
{
super(res,node);
res.properties.getUpdateSource(PBackground.USE_AS_TILESET).addListener(bpl);
res.reference.updateSource.addListener(this);
this.setLayout(new BorderLayout());
preview = new BackgroundPreview(res);
preview.setVerticalAlignment(SwingConstants.TOP);
mouseListener = new MouseListener()
{
@Override
public void mouseClicked(MouseEvent ev)
{
//preview.setCursor(LGM.zoomCursor);
}
@Override
public void mouseEntered(MouseEvent ev)
{
//preview.setCursor(LGM.zoomCursor);
}
@Override
public void mouseExited(MouseEvent ev)
{
//preview.setCursor(Cursor.getDefaultCursor());
}
@Override
public void mousePressed(MouseEvent ev)
{
if (ev.getButton() == MouseEvent.BUTTON1)
{
preview.setCursor(LGM.zoomInCursor);
}
if (ev.getButton() == MouseEvent.BUTTON3)
{
preview.setCursor(LGM.zoomOutCursor);
}
}
@Override
public void mouseReleased(MouseEvent ev)
{
if (ev.getButton() == MouseEvent.BUTTON1)
{
zoomIn(ev.getPoint());
}
if (ev.getButton() == MouseEvent.BUTTON3)
{
zoomOut(ev.getPoint());
}
preview.setCursor(LGM.zoomCursor);
}
};
previewScroll = new JScrollPane(preview);
this.add(makeToolBar(),BorderLayout.NORTH);
JSplitPane orientationSplit = new JSplitPane();
if (Prefs.rightOrientation) {
orientationSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
previewScroll,makeOptionsPanel());
orientationSplit.setResizeWeight(1d);
} else {
orientationSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
makeOptionsPanel(),previewScroll);
}
this.add(orientationSplit, BorderLayout.CENTER);
this.add(makeStatusBar(),BorderLayout.SOUTH);
updateStatusBar();
updateScrollBars();
pack();
this.setSize(640,400);
}
private JButton makeJButton(String key)
{
JButton but = new JButton(LGM.getIconForKey(key));
but.setToolTipText(Messages.getString(key));
but.addActionListener(this);
but.setActionCommand(key);
return but;
}
private JToolBar makeToolBar()
{
JToolBar tool = new JToolBar();
tool.setFloatable(false);
tool.setAlignmentX(0);
tool.add(save);
tool.addSeparator();
tool.add(makeJButton("BackgroundFrame.CREATE"));
tool.add(makeJButton("BackgroundFrame.LOAD"));
tool.add(makeJButton("BackgroundFrame.SAVE"));
tool.add(makeJButton("BackgroundFrame.EDIT"));
tool.add(makeJButton("BackgroundFrame.EFFECT"));
tool.addSeparator();
// TODO: Implement undo/redo
//tool.add(makeJButton("BackgroundFrame.UNDO"));
//tool.add(makeJButton("BackgroundFrame.REDO"));
//tool.addSeparator();
zoomButton = new JToggleButton(LGM.getIconForKey("BackgroundFrame.ZOOM"));
zoomButton.setToolTipText(Messages.getString("BackgroundFrame.ZOOM"));
zoomButton.addActionListener(this);
zoomButton.setActionCommand("BackgroundFrame.ZOOM");
tool.add(zoomButton);
tool.add(makeJButton("BackgroundFrame.ZOOM_IN"));
tool.add(makeJButton("BackgroundFrame.ZOOM_OUT"));
tool.addSeparator();
name.setColumns(13);
name.setMaximumSize(name.getPreferredSize());
tool.add(new JLabel(Messages.getString("BackgroundFrame.NAME"))); //$NON-NLS-1$
tool.add(name);
return tool;
}
private JPanel makeStatusBar()
{
JPanel status = new JPanel();
BoxLayout layout = new BoxLayout(status,BoxLayout.X_AXIS);
status.setLayout(layout);
status.setMaximumSize(new Dimension(Integer.MAX_VALUE,11));
statusLabel = new JLabel();
status.add(statusLabel);
return status;
}
private JPanel makeOptionsPanel()
{
JPanel panel = new JPanel(new BorderLayout());
GroupLayout layout = new GroupLayout(panel);
layout.setAutoCreateContainerGaps(true);
panel.setLayout(layout);
transparent = new JCheckBox(Messages.getString("BackgroundFrame.TRANSPARENT")); //$NON-NLS-1$
plf.make(transparent,PBackground.TRANSPARENT);
smooth = new JCheckBox(Messages.getString("BackgroundFrame.SMOOTH")); //$NON-NLS-1$
plf.make(smooth,PBackground.SMOOTH_EDGES);
preload = new JCheckBox(Messages.getString("BackgroundFrame.PRELOAD")); //$NON-NLS-1$
plf.make(preload,PBackground.PRELOAD);
tileset = new JCheckBox(Messages.getString("BackgroundFrame.USE_AS_TILESET")); //$NON-NLS-1$
plf.make(tileset,PBackground.USE_AS_TILESET);
panel.add(transparent);
panel.add(smooth);
panel.add(preload);
panel.add(tileset);
JPanel groupPanel = new JPanel();
GroupLayout pLayout = new GroupLayout(groupPanel);
groupPanel.setLayout(pLayout);
String tileProps = Messages.getString("BackgroundFrame.TILE_PROPERTIES"); //$NON-NLS-1$
groupPanel.setBorder(BorderFactory.createTitledBorder(tileProps));
JLabel twLabel = new JLabel(Messages.getString("BackgroundFrame.TILE_WIDTH")); //$NON-NLS-1$
twLabel.setHorizontalAlignment(SwingConstants.RIGHT);
tWidth = new NumberField(0,Integer.MAX_VALUE);
plf.make(tWidth,PBackground.TILE_WIDTH);
tWidth.setColumns(3);
JLabel thLabel = new JLabel(Messages.getString("BackgroundFrame.TILE_HEIGHT")); //$NON-NLS-1$
thLabel.setHorizontalAlignment(SwingConstants.RIGHT);
tHeight = new NumberField(0,Integer.MAX_VALUE);
plf.make(tHeight,PBackground.TILE_HEIGHT);
tHeight.setColumns(3);
JLabel hoLabel = new JLabel(Messages.getString("BackgroundFrame.H_OFFSET")); //$NON-NLS-1$
hoLabel.setHorizontalAlignment(SwingConstants.RIGHT);
hOffset = new NumberField(0,Integer.MAX_VALUE);
plf.make(hOffset,PBackground.H_OFFSET);
hOffset.setColumns(3);
JLabel voLabel = new JLabel(Messages.getString("BackgroundFrame.V_OFFSET")); //$NON-NLS-1$
voLabel.setHorizontalAlignment(SwingConstants.RIGHT);
vOffset = new NumberField(0,Integer.MAX_VALUE);
plf.make(vOffset,PBackground.V_OFFSET);
vOffset.setColumns(3);
JLabel hsLabel = new JLabel(Messages.getString("BackgroundFrame.H_SEP")); //$NON-NLS-1$
hsLabel.setHorizontalAlignment(SwingConstants.RIGHT);
hSep = new NumberField(0,Integer.MAX_VALUE);
plf.make(hSep,PBackground.H_SEP);
hSep.setColumns(3);
JLabel vsLabel = new JLabel(Messages.getString("BackgroundFrame.V_SEP")); //$NON-NLS-1$
vsLabel.setHorizontalAlignment(SwingConstants.RIGHT);
vSep = new NumberField(0,Integer.MAX_VALUE);
plf.make(vSep,PBackground.V_SEP);
vSep.setColumns(3);
pLayout.setHorizontalGroup(pLayout.createSequentialGroup()
/**/.addContainerGap(4,4)
/**/.addGroup(pLayout.createParallelGroup(Alignment.TRAILING)
/* */.addComponent(twLabel)
/* */.addComponent(thLabel)
/* */.addComponent(hoLabel)
/* */.addComponent(voLabel)
/* */.addComponent(hsLabel)
/* */.addComponent(vsLabel))
/**/.addGap(4)
/**/.addGroup(pLayout.createParallelGroup()
/* */.addComponent(tWidth,PREFERRED_SIZE,DEFAULT_SIZE,DEFAULT_SIZE)
/* */.addComponent(tHeight,PREFERRED_SIZE,DEFAULT_SIZE,DEFAULT_SIZE)
/* */.addComponent(hOffset,PREFERRED_SIZE,DEFAULT_SIZE,DEFAULT_SIZE)
/* */.addComponent(vOffset,PREFERRED_SIZE,DEFAULT_SIZE,DEFAULT_SIZE)
/* */.addComponent(hSep,PREFERRED_SIZE,DEFAULT_SIZE,DEFAULT_SIZE)
/* */.addComponent(vSep,PREFERRED_SIZE,DEFAULT_SIZE,DEFAULT_SIZE))
/**/.addContainerGap(4,4));
pLayout.setVerticalGroup(pLayout.createSequentialGroup()
/**/.addGap(2)
/**/.addGroup(pLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(twLabel)
/* */.addComponent(tWidth))
/**/.addGap(2)
/**/.addGroup(pLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(thLabel)
/* */.addComponent(tHeight))
/**/.addGap(8)
/**/.addGroup(pLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(hoLabel)
/* */.addComponent(hOffset))
/**/.addGap(2)
/**/.addGroup(pLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(voLabel)
/* */.addComponent(vOffset))
/**/.addGap(8)
/**/.addGroup(pLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(hsLabel)
/* */.addComponent(hSep))
/**/.addGap(2)
/**/.addGroup(pLayout.createParallelGroup(Alignment.BASELINE)
/* */.addComponent(vsLabel)
/* */.addComponent(vSep))
/**/.addContainerGap(8,8));
//groupPanel.setVisible(tileset.isSelected());
panel.add(groupPanel);
layout.setHorizontalGroup(layout.createParallelGroup()
/**/.addComponent(smooth)
/**/.addComponent(preload)
/**/.addComponent(transparent)
/**/.addComponent(tileset)
/**/.addComponent(groupPanel));
layout.setVerticalGroup(layout.createSequentialGroup()
/**/.addComponent(smooth)
/**/.addComponent(preload)
/**/.addComponent(transparent)
/**/.addComponent(tileset)
/**/.addGap(8)
/**/.addComponent(groupPanel));
return panel;
}
private void realizeScrollBarIncrement(JScrollPane scroll)
{
JScrollBar vertical = scroll.getVerticalScrollBar();
JScrollBar horizontal = scroll.getHorizontalScrollBar();
if (vertical != null)
{
vertical.setUnitIncrement((int) getZoom());
}
if (horizontal != null)
{
horizontal.setUnitIncrement((int) getZoom());
}
}
private void updateScrollBars()
{
realizeScrollBarIncrement(previewScroll);
}
public double getZoom()
{
return preview.getZoom();
}
public void setZoom(double nzoom)
{
preview.setZoom(nzoom);
updateStatusBar();
updateScrollBars();
}
private void updateStatusBar()
{
String stat = " " + Messages.getString("BackgroundFrame.WIDTH") + ": " + res.getWidth() + " | "
+ Messages.getString("BackgroundFrame.HEIGHT") + ": " + res.getHeight() + " | "
+ Messages.getString("BackgroundFrame.MEMORY") + ": ";
if (res.getBackgroundImage() != null)
{
stat += Util.formatDataSize(res.getSize());
}
else
{
stat += Util.formatDataSize(0);
}
String zoom = new DecimalFormat("#,##0.##").format(getZoom() * 100);
stat += " | " + Messages.getString("BackgroundFrame.ZOOM") + ": " + zoom + "%";
statusLabel.setText(stat);
}
protected boolean areResourceFieldsEqual()
{
return !imageChanged;
}
public void commitChanges()
{
res.setName(name.getText());
}
@Override
public void updateResource(boolean commit)
{
super.updateResource(commit);
imageChanged = false;
updateStatusBar();
updateScrollBars();
}
public void handleToolBar(String cmd)
{
if (cmd.endsWith(".LOAD"))
{
BufferedImage img = Util.getValidImage();
if (img != null)
{
res.setBackgroundImage(img);
imageChanged = true;
cleanup();
}
return;
}
else if (cmd.endsWith(".SAVE"))
{
BufferedImage img = res.getBackgroundImage();
// utility function will check if the image is null and display an appropriate warning
// telling the user to create the image before saving
Util.saveImage(img);
return;
}
else if (cmd.endsWith(".CREATE"))
{
createNewImage(true);
}
else if (cmd.endsWith(".EDIT"))
{
try
{
if (editor == null)
new BackgroundEditor();
else
editor.start();
}
catch (IOException ex)
{
LGM.showDefaultExceptionHandler(ex);
}
return;
}
else if (cmd.endsWith(".EFFECT")) {
List<BufferedImage> imgs = new ArrayList<BufferedImage>(1);
imgs.add(res.getBackgroundImage());
EffectsFrame ef = EffectsFrame.getInstance(imgs);
ef.setEffectsListener(this);
ef.setVisible(true);
}
else if (cmd.endsWith(".ZOOM"))
{
if (zoomButton.isSelected())
{
preview.setCursor(LGM.zoomCursor);
preview.addMouseListener(mouseListener);
}
else
{
preview.removeMouseListener(mouseListener);
preview.setCursor(Cursor.getDefaultCursor());
}
}
else if (cmd.endsWith(".ZOOM_IN"))
{
zoomIn();
return;
}
else if (cmd.endsWith(".ZOOM_OUT"))
{
zoomOut();
return;
}
}
public void actionPerformed(ActionEvent e)
{
handleToolBar(e.getActionCommand());
super.actionPerformed(e);
}
@Override
public Dimension getMinimumSize()
{
Dimension p = getContentPane().getSize();
Dimension l = getContentPane().getMinimumSize();
Dimension s = getSize();
l.width += s.width - p.width;
l.height += s.height - p.height;
return l;
}
private BufferedImage createNewImage(boolean askforsize)
{
int width = 256;
int height = 256;
if (askforsize)
{
NumberFormatter nf = new NumberFormatter();
nf.setMinimum(new Integer(1));
JFormattedTextField wField = new JFormattedTextField(nf);
wField.setValue(new Integer(width));
JFormattedTextField hField = new JFormattedTextField(nf);
hField.setValue(new Integer(height));
JPanel myPanel = new JPanel();
GridLayout layout = new GridLayout(0,2,0,3);
myPanel.setLayout(layout);
myPanel.add(new JLabel(Messages.getString("BackgroundFrame.NEW_WIDTH")));
myPanel.add(wField);
myPanel.add(new JLabel(Messages.getString("BackgroundFrame.NEW_HEIGHT")));
myPanel.add(hField);
int result = JOptionPane.showConfirmDialog(LGM.frame,myPanel,Messages.getString("BackgroundFrame.NEW_TITLE"),
JOptionPane.OK_CANCEL_OPTION,JOptionPane.PLAIN_MESSAGE);
if (result == JOptionPane.CANCEL_OPTION)
{
return null;
}
width = (Integer) wField.getValue();
height = (Integer) hField.getValue();
}
BufferedImage bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
res.setBackgroundImage(bi);
imageChanged = true;
return bi;
}
private class BackgroundEditor implements UpdateListener
{
public final FileChangeMonitor monitor;
private final File f;
public BackgroundEditor() throws IOException
{
f = File.createTempFile(res.getName(),
"." + Prefs.externalBackgroundExtension,LGM.tempDir); //$NON-NLS-1$
f.deleteOnExit();
monitor = new FileChangeMonitor(f,SwingExecutor.INSTANCE);
monitor.updateSource.addListener(this);
editor = this;
start();
}
public void start() throws IOException
{
BufferedImage bi = res.getBackgroundImage();
if (bi == null)
{
bi = createNewImage(false);
}
FileOutputStream out = null;
try
{
out = new FileOutputStream(f);
ImageIO.write(bi,Prefs.externalBackgroundExtension,out);
}
finally
{
if (out != null)
{
out.close();
}
}
if (!Prefs.useExternalBackgroundEditor || Prefs.externalBackgroundEditorCommand == null)
try
{
Desktop.getDesktop().edit(monitor.file);
}
catch (UnsupportedOperationException e)
{
throw new UnsupportedOperationException("no internal or system background editor",e);
}
else
Runtime.getRuntime().exec(
String.format(Prefs.externalBackgroundEditorCommand,monitor.file.getAbsolutePath()));
}
public void stop()
{
monitor.stop();
monitor.file.delete();
editor = null;
}
public void updated(UpdateEvent e)
{
if (!(e instanceof FileUpdateEvent)) return;
switch (((FileUpdateEvent) e).flag)
{
case CHANGED:
BufferedImage img;
FileInputStream stream = null;
try
{
stream = new FileInputStream(monitor.file);
img = ImageIO.read(stream);
}
catch (IOException ioe)
{
LGM.showDefaultExceptionHandler(ioe);
return;
}
finally
{
if (stream != null) {
try
{
stream.close();
}
catch (IOException ex)
{
LGM.showDefaultExceptionHandler(ex);
}
}
}
res.setBackgroundImage(img);
imageChanged = true;
break;
case DELETED:
editor = null;
}
}
}
public void dispose()
{
cleanup();
super.dispose();
}
protected void cleanup()
{
if (editor != null) editor.stop();
}
public void updated(UpdateEvent e)
{
updateStatusBar();
updateScrollBars();
}
private class BackgroundPropertyListener extends PropertyUpdateListener<PBackground>
{
public void updated(PropertyUpdateEvent<PBackground> e)
{
//TODO: Maybe remove this
//USE_AS_TILESET
//side2.setVisible((Boolean)
//res.get(PBackground.USE_AS_TILESET));
}
}
@Override
public void applyEffects(List<BufferedImage> imgs)
{
res.setBackgroundImage(imgs.get(0));
}
}