/* * Copyright (C) 2007 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2008 Quadduc <quadduc@gmail.com> * Copyright (C) 2013 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 java.lang.Integer.MAX_VALUE; import static javax.swing.GroupLayout.DEFAULT_SIZE; import static javax.swing.GroupLayout.PREFERRED_SIZE; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Collections; import javax.swing.GroupLayout; import javax.swing.GroupLayout.Alignment; import javax.swing.GroupLayout.ParallelGroup; import javax.swing.GroupLayout.SequentialGroup; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.lateralgm.components.ActionList; import org.lateralgm.components.ActionListEditor; import org.lateralgm.components.CodeTextArea; import org.lateralgm.components.NumberField; import org.lateralgm.components.ActionList.ActionListModel; import org.lateralgm.components.impl.ResNode; import org.lateralgm.components.mdi.MDIFrame; import org.lateralgm.main.LGM; import org.lateralgm.main.Prefs; import org.lateralgm.main.Util; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Timeline; import org.lateralgm.resources.Timeline.PTimeline; import org.lateralgm.resources.library.LibAction; import org.lateralgm.resources.library.LibManager; import org.lateralgm.resources.sub.Action; import org.lateralgm.resources.sub.Moment; public class TimelineFrame extends InstantiableResourceFrame<Timeline,PTimeline> implements ListSelectionListener { private static final long serialVersionUID = 1L; public JButton add, edit, change, delete, duplicate; public JButton shift, merge, clear, showInfo; public JList<Moment> moments; public ActionList actions; public CodeTextArea code; private JComponent editor; private ResourceInfoFrame infoFrame; public TimelineFrame(Timeline res, ResNode node) { super(res,node); GroupLayout layout = new GroupLayout(getContentPane()); setLayout(layout); JPanel propertiesSide = new JPanel(); makeProperties(propertiesSide); JPanel momentsSide = new JPanel(new BorderLayout()); //side2.setMaximumSize(new Dimension(90,Integer.MAX_VALUE)); JLabel lab = new JLabel(Messages.getString("TimelineFrame.MOMENTS")); //$NON-NLS-1$ momentsSide.add(lab,BorderLayout.NORTH); moments = new JList<Moment>(res.moments.toArray(new Moment[res.moments.size()])); moments.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); moments.addListSelectionListener(this); // This listener should be added to each node maybe // otherwise you can click on the whitespace and open it // but then again I suppose its fine like this because I have // ensured checks to make sure there are no NPE's trying to edit // an event that don't exist. Meh, this is fine as is. MouseListener ml = new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.getClickCount() == 2) { editSelectedMoment(); } } }; moments.addMouseListener(ml); JScrollPane scroll = new JScrollPane(moments); if (Prefs.enableDragAndDrop) { scroll.setPreferredSize(new Dimension(100,300)); } else { scroll.setPreferredSize(new Dimension(200,400)); } momentsSide.add(scroll,BorderLayout.CENTER); actions = new ActionList(this); if (Prefs.enableDragAndDrop) { editor = new ActionListEditor(actions); } SequentialGroup orientationGroup = layout.createSequentialGroup(); if (Prefs.rightOrientation) { if (Prefs.enableDragAndDrop) { orientationGroup.addComponent(editor); } orientationGroup.addComponent(momentsSide) /**/.addComponent(propertiesSide,DEFAULT_SIZE,PREFERRED_SIZE,PREFERRED_SIZE); } else { orientationGroup.addComponent(propertiesSide,DEFAULT_SIZE,PREFERRED_SIZE,PREFERRED_SIZE) /**/.addComponent(momentsSide); if (Prefs.enableDragAndDrop) { orientationGroup.addComponent(editor); } } layout.setHorizontalGroup(orientationGroup); ParallelGroup verticalGroup = layout.createParallelGroup() /**/.addComponent(propertiesSide) /**/.addComponent(momentsSide); if (Prefs.enableDragAndDrop) { verticalGroup.addComponent(editor); } layout.setVerticalGroup(verticalGroup); pack(); moments.setSelectedIndex(0); } private void makeProperties(JPanel side1) { GroupLayout layout = new GroupLayout(side1); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); side1.setLayout(layout); JLabel lab = new JLabel(Messages.getString("TimelineFrame.NAME")); //$NON-NLS-1$ edit = new JButton(Messages.getString("TimelineFrame.EDIT")); //$NON-NLS-1$ edit.addActionListener(this); add = new JButton(Messages.getString("TimelineFrame.ADD")); //$NON-NLS-1$ add.addActionListener(this); change = new JButton(Messages.getString("TimelineFrame.CHANGE")); //$NON-NLS-1$ change.addActionListener(this); delete = new JButton(Messages.getString("TimelineFrame.DELETE")); //$NON-NLS-1$ delete.addActionListener(this); duplicate = new JButton(Messages.getString("TimelineFrame.DUPLICATE")); //$NON-NLS-1$ duplicate.addActionListener(this); shift = new JButton(Messages.getString("TimelineFrame.SHIFT")); //$NON-NLS-1$ shift.addActionListener(this); merge = new JButton(Messages.getString("TimelineFrame.MERGE")); //$NON-NLS-1$ merge.addActionListener(this); clear = new JButton(Messages.getString("TimelineFrame.CLEAR")); //$NON-NLS-1$ clear.addActionListener(this); showInfo = new JButton(Messages.getString("TimelineFrame.SHOWINFORMATION")); //$NON-NLS-1$ showInfo.addActionListener(this); showInfo.setIcon(LGM.getIconForKey("TimelineFrame.SHOWINFORMATION")); save.setText(Messages.getString("TimelineFrame.SAVE")); //$NON-NLS-1$ layout.setHorizontalGroup(layout.createParallelGroup() /**/.addGroup(layout.createSequentialGroup() /* */.addComponent(lab) /* */.addComponent(name,DEFAULT_SIZE,120,MAX_VALUE)) /**/.addComponent(edit,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE) /**/.addGroup(layout.createSequentialGroup() /* */.addGroup(layout.createParallelGroup() /* */.addComponent(add,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE) /* */.addComponent(delete,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE)) /* */.addGroup(layout.createParallelGroup() /* */.addComponent(change,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE) /* */.addComponent(duplicate,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE))) /**/.addGroup(layout.createSequentialGroup() /* */.addComponent(shift,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE) /* */.addComponent(merge,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE)) /**/.addComponent(clear,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE) /**/.addComponent(showInfo,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE) /**/.addComponent(save,DEFAULT_SIZE,DEFAULT_SIZE,MAX_VALUE)); layout.setVerticalGroup(layout.createSequentialGroup() /**/.addGroup(layout.createParallelGroup(Alignment.BASELINE) /* */.addComponent(lab) /* */.addComponent(name)) /**/.addGap(32) /**/.addComponent(edit) /**/.addGroup(layout.createParallelGroup(Alignment.BASELINE) /* */.addComponent(add) /* */.addComponent(change)) /**/.addGroup(layout.createParallelGroup(Alignment.BASELINE) /* */.addComponent(delete) /* */.addComponent(duplicate)) /**/.addGap(32) /**/.addGroup(layout.createParallelGroup(Alignment.BASELINE) /* */.addComponent(shift) /* */.addComponent(merge)) /**/.addComponent(clear) /**/.addGap(8,8,MAX_VALUE) /**/.addComponent(showInfo) /**/.addGap(32,32,MAX_VALUE) /**/.addComponent(save)); } protected boolean areResourceFieldsEqual() // return new ResourceComparator().areEqual(res.moments,resOriginal.moments); { return Util.areInherentlyUniquesEqual(res.moments,resOriginal.moments); } public void commitChanges() { actions.save(); res.setName(name.getText()); } public int findMoment(int step) { ListModel<Moment> model = moments.getModel(); for (int i = 0; i < model.getSize(); i++){ Moment mom = model.getElementAt(i); if (mom.stepNo == step) { return i; } } return -1; } public void setSelectedMoment(int step) { int index = findMoment(step); if (index != -1) { moments.setSelectedIndex(index); } } public void showInfoFrame() { // The first code here is a little routine to realize all changes to every moments actions // NOTE: This does affect reverting the resource, just makes it so // the info frame can use the up to date version. ListModel<Moment> mommodel = moments.getModel(); if (mommodel.getSize() > 0) { Moment node = (Moment) mommodel.getElementAt(moments.getSelectedIndex()); if (node != null) { actions.setActionContainer(node); } } if (infoFrame == null) { infoFrame = new ResourceInfoFrame(); } infoFrame.updateTimelineInfo(res.reference); infoFrame.setVisible(true); } @Override public void dispose() { super.dispose(); if (infoFrame != null) { infoFrame.dispose(); } ((ActionListEditor) editor).dispose(); } public void actionPerformed(ActionEvent e) { if (!(e.getSource() instanceof JButton)) { super.actionPerformed(e); return; } JButton but = (JButton) e.getSource(); if (but == add || but == change || but == duplicate) { Moment m = (Moment) moments.getSelectedValue(); if (m == null && but != add) return; int sn = (m == null) ? -1 : m.stepNo; String msg = Messages.getString("TimelineFrame.MOM_NUM"); //$NON-NLS-1$ String ttl; if (but == add) ttl = "TimelineFrame.MOM_ADD"; //$NON-NLS-1$ else if (but == change) ttl = "TimelineFrame.MOM_CHANGE"; //$NON-NLS-1$ else ttl = "TimelineFrame.MOM_DUPLICATE"; //$NON-NLS-1$ ttl = Messages.getString(ttl); JPanel pane = new JPanel(); pane.add(new JLabel(msg)); NumberField field = new NumberField(sn + (but == add ? 1 : 0)); // field.setPreferredSize(new Dimension(80,20)); pane.add(field); int ret = JOptionPane.showConfirmDialog(this,pane,ttl,JOptionPane.OK_CANCEL_OPTION); if (ret == JOptionPane.CANCEL_OPTION) return; ret = field.getIntValue(); if (ret == sn) return; int p = -Collections.binarySearch(res.moments,ret) - 1; if (but == add) { if (p < 0) { moments.setSelectedIndex(-p); return; } Moment m2 = new Moment(); m2.stepNo = ret; res.moments.add(p,m2); } else if (but == change) { if (p < 0) { JOptionPane.showMessageDialog(this,Messages.getString("TimelineFrame.MOM_EXIST")); //$NON_NLS-1$ //$NON-NLS-1$ return; } if (ret > sn) p--; m.stepNo = ret; res.moments.remove(moments.getSelectedIndex()); //should never be -1 res.moments.add(p,m); } else { if (p >= 0) { JOptionPane.showMessageDialog(this,Messages.getString("TimelineFrame.MOM_EXIST")); //$NON_NLS-1$ //$NON-NLS-1$ return; } Moment m2 = m.copy(); m2.stepNo = ret; res.moments.add(p,m2); } moments.setListData(res.moments.toArray(new Moment[res.moments.size()])); moments.setSelectedIndex(p); return; } if (but == delete) { int p = moments.getSelectedIndex(); if (p == -1) return; if (res.moments.get(p).actions.size() != 0) { String msg = Messages.getString("TimelineFrame.MOM_DELETE"); //$NON-NLS-1$ String ttl = Messages.getString("TimelineFrame.MOM_CONFIRM"); //$NON-NLS-1$ int r = JOptionPane.showConfirmDialog(this,msg,ttl,JOptionPane.YES_NO_OPTION); if (r == JOptionPane.NO_OPTION) return; } res.moments.remove(p); moments.setListData(res.moments.toArray(new Moment[res.moments.size()])); moments.setSelectedIndex(Math.min(res.moments.size() - 1,p)); return; } if (but == showInfo) { showInfoFrame(); return; } if (but == edit) { editSelectedMoment(); return; } if (but == clear) { if (res.moments.size() == 0) return; String msg = Messages.getString("TimelineFrame.MOM_CLEAR"); //$NON-NLS-1$ String ttl = Messages.getString("TimelineFrame.MOM_CONFIRM"); //$NON-NLS-1$ int r = JOptionPane.showConfirmDialog(this,msg,ttl,JOptionPane.YES_NO_OPTION); if (r == JOptionPane.NO_OPTION) return; res.moments.clear(); moments.setListData(res.moments.toArray(new Moment[res.moments.size()])); } if (but == shift) { if (res.moments.size() == 0) return; Moment m = (Moment) moments.getSelectedValue(); int sn = (m == null) ? 0 : m.stepNo; String ttl = Messages.getString("TimelineFrame.MOM_SHIFT"); //$NON-NLS-1$ JPanel pane = new JPanel(new GridLayout(0,2)); pane.add(new JLabel(Messages.getString("TimelineFrame.MOM_START"))); //$NON-NLS-1$ NumberField iStart = new NumberField(sn); iStart.setPreferredSize(new Dimension(80,20)); pane.add(iStart); pane.add(new JLabel(Messages.getString("TimelineFrame.MOM_END"))); //$NON-NLS-1$ NumberField iEnd = new NumberField(sn); iEnd.setPreferredSize(new Dimension(80,20)); pane.add(iEnd); pane.add(new JLabel(Messages.getString("TimelineFrame.MOM_AMOUNT"))); //$NON-NLS-1$ NumberField iAmt = new NumberField(1); iAmt.setPreferredSize(new Dimension(80,20)); pane.add(iAmt); int ret = JOptionPane.showConfirmDialog(this,pane,ttl,JOptionPane.OK_CANCEL_OPTION); if (ret == JOptionPane.CANCEL_OPTION) return; int p = res.shiftMoments(iStart.getIntValue(),iEnd.getIntValue(),iAmt.getIntValue()); moments.setListData(res.moments.toArray(new Moment[res.moments.size()])); //this is actually the *old* position of first shifted moment, the same as GM does it. //if we wanted to, we could find the new position, but it's a lot of work for nothing moments.setSelectedIndex(p); return; } if (but == merge) { if (res.moments.size() == 0) return; Moment m = (Moment) moments.getSelectedValue(); int sn = (m == null) ? 0 : m.stepNo; String ttl = Messages.getString("TimelineFrame.MOM_MERGE"); //$NON-NLS-1$ JPanel pane = new JPanel(new GridLayout(0,2)); pane.add(new JLabel(Messages.getString("TimelineFrame.MOM_START"))); //$NON-NLS-1$ NumberField iStart = new NumberField(0,Integer.MAX_VALUE,sn); iStart.setPreferredSize(new Dimension(80,20)); pane.add(iStart); pane.add(new JLabel(Messages.getString("TimelineFrame.MOM_END"))); //$NON-NLS-1$ NumberField iEnd = new NumberField(0,Integer.MAX_VALUE,sn); iEnd.setPreferredSize(new Dimension(80,20)); pane.add(iEnd); int ret = JOptionPane.showConfirmDialog(this,pane,ttl,JOptionPane.OK_CANCEL_OPTION); if (ret == JOptionPane.CANCEL_OPTION) return; actions.save(); //prevents "fresh" actions from being overwritten int p = res.mergeMoments(iStart.getIntValue(),iEnd.getIntValue()); moments.setListData(res.moments.toArray(new Moment[res.moments.size()])); moments.setSelectedIndex(p); return; } super.actionPerformed(e); } private void editSelectedMoment() { int p = moments.getSelectedIndex(); if (p == -1) return; Action a = null; LibAction la = null; Boolean prependNew = true; for (int i = 0; i < actions.model.list.size(); i++) { a = actions.model.list.get(i); la = a.getLibAction(); if (la.actionKind == Action.ACT_CODE) { prependNew = false; break; } } if (prependNew) { a = new Action(LibManager.codeAction); ((ActionListModel) actions.getModel()).add(0,a); actions.setSelectedValue(a,true); } MDIFrame af = ActionList.openActionFrame(actions.parent.get(),a); Object momentitem = moments.getSelectedValue(); af.setTitle(this.name.getText() + " : " + momentitem.toString()); af.setFrameIcon(LGM.getIconForKey("MomentNode.STEP")); return; } //Moments selection changed public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; Moment m = (Moment) moments.getSelectedValue(); actions.setActionContainer(m); } @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; } }