diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index fc8b8cf9e..5d7d0a86e 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -24,6 +24,7 @@ import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; +import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; @@ -61,6 +62,7 @@ import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; import javax.swing.DropMode; +import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; @@ -91,12 +93,15 @@ import javax.swing.WindowConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.text.JTextComponent; import org.infinity.NearInfinity; import org.infinity.gui.ButtonPopupMenu; +import org.infinity.gui.ButtonPopupWindow; import org.infinity.gui.ChildFrame; import org.infinity.gui.DataMenuItem; import org.infinity.gui.FixedFocusTraversalPolicy; @@ -240,10 +245,16 @@ public class ConvertToBam extends ChildFrame implements ActionListener, Property private JButton bMacroReverseFrames; private JButton bMacroRemoveAll; private JButton bMacroReverseCycles; + private JButton bPreviewCycleFirst; private JButton bPreviewCyclePrev; private JButton bPreviewCycleNext; + private JButton bPreviewCycleLast; + private ButtonPopupWindow bpwPreviewCycleGoto; + private PreviewGotoCyclePanel previewGotoCyclePanel; + private JButton bPreviewFrameFirst; private JButton bPreviewFramePrev; private JButton bPreviewFrameNext; + private JButton bPreviewFrameLast; private JButton bPreviewPlay; private JButton bPreviewStop; private JButton bFiltersAdd; @@ -713,14 +724,29 @@ protected List doInBackground() throws Exception { currentCycleMoveUp(); } else if (event.getSource() == bCurCycleDown) { currentCycleMoveDown(); + } else if (event.getSource() == bPreviewCycleFirst) { + previewCycleStart(); } else if (event.getSource() == bPreviewCyclePrev) { previewCycleDown(); } else if (event.getSource() == bPreviewCycleNext) { previewCycleUp(); + } else if (event.getSource() == bPreviewCycleLast) { + previewCycleEnd(); + } else if (event.getSource() == previewGotoCyclePanel) { + if (PreviewGotoCyclePanel.ACTION_ACCEPT.equals(event.getActionCommand())) { + previewSetCycle(previewGotoCyclePanel.getValue()); + } + bpwPreviewCycleGoto.hidePopupWindow(); + } else if (event.getSource() == bpwPreviewCycleGoto) { + previewCycleGotoInit(); + } else if (event.getSource() == bPreviewFrameFirst) { + previewFrameStart(); } else if (event.getSource() == bPreviewFramePrev) { previewFrameDown(); } else if (event.getSource() == bPreviewFrameNext) { previewFrameUp(); + } else if (event.getSource() == bPreviewFrameLast) { + previewFrameEnd(); } else if (event.getSource() == bPreviewPlay) { previewPlay(); } else if (event.getSource() == bPreviewStop) { @@ -1604,24 +1630,50 @@ private JPanel createPreviewTab() { // create bottom control bar lPreviewCycle = new JLabel("Cycle: X/Y"); + bPreviewCycleFirst = new JButton(Icons.ICON_FIRST_16.getIcon()); + bPreviewCycleFirst + .setMargin(new Insets(bPreviewCycleFirst.getMargin().top, 8, bPreviewCycleFirst.getMargin().bottom, 8)); + bPreviewCycleFirst.addActionListener(this); bPreviewCyclePrev = new JButton(Icons.ICON_BACK_16.getIcon()); bPreviewCyclePrev - .setMargin(new Insets(bPreviewCyclePrev.getMargin().top, 2, bPreviewCyclePrev.getMargin().bottom, 2)); + .setMargin(new Insets(bPreviewCyclePrev.getMargin().top, 8, bPreviewCyclePrev.getMargin().bottom, 8)); bPreviewCyclePrev.addActionListener(this); bPreviewCycleNext = new JButton(Icons.ICON_FORWARD_16.getIcon()); bPreviewCycleNext - .setMargin(new Insets(bPreviewCycleNext.getMargin().top, 2, bPreviewCycleNext.getMargin().bottom, 2)); + .setMargin(new Insets(bPreviewCycleNext.getMargin().top, 8, bPreviewCycleNext.getMargin().bottom, 8)); bPreviewCycleNext.addActionListener(this); + bPreviewCycleLast = new JButton(Icons.ICON_LAST_16.getIcon()); + bPreviewCycleLast + .setMargin(new Insets(bPreviewCycleLast.getMargin().top, 8, bPreviewCycleLast.getMargin().bottom, 8)); + bPreviewCycleLast.addActionListener(this); + + // TODO: replace JPanel by a custom "Goto index" input mask + previewGotoCyclePanel = new PreviewGotoCyclePanel(); + previewGotoCyclePanel.addActionListener(this); + bpwPreviewCycleGoto = new ButtonPopupWindow("", Icons.ICON_GOTO_24.getIcon(), previewGotoCyclePanel, + ButtonPopupWindow.Align.TOP); + bpwPreviewCycleGoto + .setMargin(new Insets(bpwPreviewCycleGoto.getMargin().top, 4, bpwPreviewCycleGoto.getMargin().bottom, 4)); + bpwPreviewCycleGoto.setToolTipText("Jump to a specific cycle."); + bpwPreviewCycleGoto.addActionListener(this); lPreviewFrame = new JLabel("Frame: X/Y"); + bPreviewFrameFirst = new JButton(Icons.ICON_FIRST_16.getIcon()); + bPreviewFrameFirst + .setMargin(new Insets(bPreviewFrameFirst.getMargin().top, 8, bPreviewFrameFirst.getMargin().bottom, 8)); + bPreviewFrameFirst.addActionListener(this); bPreviewFramePrev = new JButton(Icons.ICON_BACK_16.getIcon()); bPreviewFramePrev - .setMargin(new Insets(bPreviewFramePrev.getMargin().top, 2, bPreviewFramePrev.getMargin().bottom, 2)); + .setMargin(new Insets(bPreviewFramePrev.getMargin().top, 8, bPreviewFramePrev.getMargin().bottom, 8)); bPreviewFramePrev.addActionListener(this); bPreviewFrameNext = new JButton(Icons.ICON_FORWARD_16.getIcon()); bPreviewFrameNext - .setMargin(new Insets(bPreviewFrameNext.getMargin().top, 2, bPreviewFrameNext.getMargin().bottom, 2)); + .setMargin(new Insets(bPreviewFrameNext.getMargin().top, 8, bPreviewFrameNext.getMargin().bottom, 8)); bPreviewFrameNext.addActionListener(this); + bPreviewFrameLast = new JButton(Icons.ICON_LAST_16.getIcon()); + bPreviewFrameLast + .setMargin(new Insets(bPreviewFrameLast.getMargin().top, 8, bPreviewFrameLast.getMargin().bottom, 8)); + bPreviewFrameLast.addActionListener(this); bPreviewPlay = new JButton("Pause", Icons.ICON_PLAY_16.getIcon()); bPreviewPlay.setMinimumSize(bPreviewPlay.getPreferredSize()); @@ -1633,25 +1685,40 @@ private JPanel createPreviewTab() { c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pControls.add(lPreviewCycle, c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 4, 0, 0), 0, 0); + pControls.add(bPreviewCycleFirst, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, + new Insets(0, 2, 0, 0), 0, 0); pControls.add(bPreviewCyclePrev, c); - c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 2, 0, 0), 0, 0); pControls.add(bPreviewCycleNext, c); - c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, + new Insets(0, 2, 0, 0), 0, 0); + pControls.add(bPreviewCycleLast, c); + c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, + new Insets(0, 4, 0, 0), 0, 0); + pControls.add(bpwPreviewCycleGoto, c); + c = ViewerUtil.setGBC(c, 6, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pControls.add(lPreviewFrame, c); - c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 7, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 4, 0, 0), 0, 0); + pControls.add(bPreviewFrameFirst, c); + c = ViewerUtil.setGBC(c, 8, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, + new Insets(0, 2, 0, 0), 0, 0); pControls.add(bPreviewFramePrev, c); - c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 9, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 2, 0, 0), 0, 0); pControls.add(bPreviewFrameNext, c); - c = ViewerUtil.setGBC(c, 6, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 10, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, + new Insets(0, 2, 0, 0), 0, 0); + pControls.add(bPreviewFrameLast, c); + c = ViewerUtil.setGBC(c, 11, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 16, 0, 0), 0, 0); pControls.add(bPreviewPlay, c); - c = ViewerUtil.setGBC(c, 7, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + c = ViewerUtil.setGBC(c, 12, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 4, 0, 0), 0, 0); pControls.add(bPreviewStop, c); @@ -2191,10 +2258,14 @@ private void updatePreview() { previewDisplay(); // updating buttons + bPreviewCycleFirst.setEnabled(bamControlPreview.cycleGet() != 0); bPreviewCyclePrev.setEnabled(bamControlPreview.cycleGet() > 0); bPreviewCycleNext.setEnabled(bamControlPreview.cycleGet() < bamControlPreview.cycleCount() - 1); + bPreviewCycleLast.setEnabled(bamControlPreview.cycleGet() != bamControlPreview.cycleCount() - 1); + bPreviewFrameFirst.setEnabled(bamControlPreview.cycleGetFrameIndex() != 0); bPreviewFramePrev.setEnabled(bamControlPreview.cycleGetFrameIndex() > 0); bPreviewFrameNext.setEnabled(bamControlPreview.cycleGetFrameIndex() < bamControlPreview.cycleFrameCount() - 1); + bPreviewFrameLast.setEnabled(bamControlPreview.cycleGetFrameIndex() != bamControlPreview.cycleFrameCount() - 1); lPreviewCycle .setText(String.format("Cycle: %d/%d", bamControlPreview.cycleGet(), bamControlPreview.cycleCount() - 1)); lPreviewFrame.setText( @@ -3302,6 +3373,40 @@ private void previewCycleDown() { } } + /** Action for "First cycle" button: selects first cycle index if available. */ + private void previewCycleStart() { + if (bamControlPreview.cycleGet() != 0) { + bamControlPreview.cycleSet(0); + bamControlPreview.cycleSetFrameIndex(0); + updatePreview(); + } + } + + /** Action for "Last cycle" button: selects last cycle index if available. */ + private void previewCycleEnd() { + if (bamControlPreview.cycleGet() != bamControlPreview.cycleCount() - 1) { + bamControlPreview.cycleSet(bamControlPreview.cycleCount() - 1); + bamControlPreview.cycleSetFrameIndex(0); + updatePreview(); + } + } + + /** Initializes the "Goto cycle" panel with the current cycle index. */ + private void previewCycleGotoInit() { + previewGotoCyclePanel.setDefaultValue(bamControlPreview.cycleGet()); + previewGotoCyclePanel.setValue(previewGotoCyclePanel.getDefaultValue()); + previewGotoCyclePanel.activate(); + } + + /** Action for "Goto cycle" button: sets the specified cycle index if available. */ + private void previewSetCycle(int cycleIdx) { + if (cycleIdx != bamControlPreview.cycleGet() && cycleIdx >= 0 && cycleIdx < bamControlPreview.cycleCount()) { + bamControlPreview.cycleSet(cycleIdx); + bamControlPreview.cycleSetFrameIndex(0); + updatePreview(); + } + } + /** Action for "Next frame" button: selects next frame index if available. */ private void previewFrameUp() { if (bamControlPreview.cycleGetFrameIndex() < bamControlPreview.cycleFrameCount() - 1) { @@ -3318,6 +3423,22 @@ private void previewFrameDown() { } } + /** Action for "First frame" button: selects first frame index if available. */ + private void previewFrameStart() { + if (bamControlPreview.cycleGetFrameIndex() != 0) { + bamControlPreview.cycleSetFrameIndex(0); + updatePreview(); + } + } + + /** Action for "Last frame" button: selects last frame index if available. */ + private void previewFrameEnd() { + if (bamControlPreview.cycleGetFrameIndex() != bamControlPreview.cycleFrameCount() - 1) { + bamControlPreview.cycleSetFrameIndex(bamControlPreview.cycleFrameCount() - 1); + updatePreview(); + } + } + /** Returns the current playback mode. */ private int previewGetMode() { return cbPreviewMode.getSelectedIndex(); @@ -5850,4 +5971,164 @@ public void actionPerformed(ActionEvent e) { setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); } } + + /** + * A custom panel for jumping directly to a specific cycle index in the preview tab. + */ + // TODO + private static class PreviewGotoCyclePanel extends JPanel { + public static final String ACTION_ACCEPT = "VALUE_ACCEPTED"; + public static final String ACTION_DISCARD = "VALUE_DISCARDED"; + + private final InputVerifier inputVerifier = new InputVerifier() { + @Override + public boolean verify(JComponent input) { + if (input instanceof JTextComponent) { + final JTextComponent tc = (JTextComponent)input; + if (!tc.getText().isEmpty()) { + try { + Integer.parseInt(tc.getText()); + } catch (NumberFormatException e) { + return false; + } + } + } + return true; + } + }; + + private final EventListenerList listenerList = new EventListenerList(); + private final JLabel label = new JLabel("Go to:"); + private final JTextField cycleInput = new JTextField(5); + + private int defValue; + + public PreviewGotoCyclePanel() { + super(new GridBagLayout()); + init(); + } + + /** Returns the default value that is used if the user entered an invalid number. */ + public int getDefaultValue() { + return defValue; + } + + /** Assigns a new default value to the panel that is used if the user entered an invalid number. */ + public void setDefaultValue(int defaultValue) { + defValue = defaultValue; + } + + /** + * Returns the numeric representation of the input field content. Returns the assigned default value if content + * could not be parsed. + */ + public int getValue() { + return getValue(getDefaultValue()); + } + + /** + * Returns the numeric representation of the input field content. Returns {@code defValue} if content could not be + * parsed. + */ + public int getValue(int defValue) { + try { + return Integer.parseInt(cycleInput.getText()); + } catch (NumberFormatException e) { + return defValue; + } + } + + /** Assigns a new value to the input field. */ + public void setValue(int newValue) { + cycleInput.setText(Integer.toString(newValue)); + } + + /** Sets focus to the input field and selects all content. */ + public void activate() { + cycleInput.selectAll(); + cycleInput.requestFocusInWindow(); + } + + /** + * Registers the specified action listener. An action event is fired if the user presses {@code ENTER} while the + * input field has the focus. + */ + public void addActionListener(ActionListener l) { + if (l != null) { + listenerList.add(ActionListener.class, l); + } + } + + /** + * Unregisters the specified action listener. An action event is fired if the user presses {@code ENTER} while the + * input field has the focus. + */ + @SuppressWarnings("unused") + public void removeActionListener(ActionListener l) { + if ((l != null)) { + listenerList.remove(ActionListener.class, l); + } + } + + /** Returns a list of all registered action listeners. */ + @SuppressWarnings("unused") + public ActionListener[] getActionListeners() { + return listenerList.getListeners(ActionListener.class); + } + + /** Fires an action event to all registered listeners. */ + protected void fireActionPerformed(String actionCommand) { + Object[] listeners = listenerList.getListenerList(); + ActionEvent e = null; + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == ActionListener.class) { + if (e == null) { + e = new ActionEvent(PreviewGotoCyclePanel.this, ActionEvent.ACTION_PERFORMED, actionCommand, + System.currentTimeMillis(), 0); + } + ((ActionListener)listeners[i + 1]).actionPerformed(e); + } + } + } + + /** Initializes UI components. */ + private void init() { + cycleInput.setToolTipText("Change value with UP/DOWN keys. Press SHIFT key to increase step size."); + cycleInput.setInputVerifier(inputVerifier); + cycleInput.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ENTER: + fireActionPerformed(ACTION_ACCEPT); + break; + case KeyEvent.VK_ESCAPE: + fireActionPerformed(ACTION_DISCARD); + break; + case KeyEvent.VK_UP: + { + final int step = (e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0 ? 10 : 1; + setValue(getValue() + step); + activate(); + break; + } + case KeyEvent.VK_DOWN: + final int step = (e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0 ? 10 : 1; + setValue(Math.max(0, getValue() - step)); + activate(); + break; + default: + } + } + }); + + GridBagConstraints c = new GridBagConstraints(); + ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(4, 4, 4, 0), 0, 0); + add(label, c); + ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(4, 4, 4, 4), 0, 0); + add(cycleInput, c); + } + } } diff --git a/src/org/infinity/icon/First16.gif b/src/org/infinity/icon/First16.gif new file mode 100644 index 000000000..8ccf86dfe Binary files /dev/null and b/src/org/infinity/icon/First16.gif differ diff --git a/src/org/infinity/icon/Goto24.gif b/src/org/infinity/icon/Goto24.gif new file mode 100644 index 000000000..79a7c3508 Binary files /dev/null and b/src/org/infinity/icon/Goto24.gif differ diff --git a/src/org/infinity/icon/Icons.java b/src/org/infinity/icon/Icons.java index 38520a92b..2855955d2 100644 --- a/src/org/infinity/icon/Icons.java +++ b/src/org/infinity/icon/Icons.java @@ -48,12 +48,15 @@ public enum Icons { ICON_FILTER_16("Filter16.png"), ICON_FIND_16("Find16.gif"), ICON_FIND_AGAIN_16("FindAgain16.gif"), + ICON_FIRST_16("First16.gif"), ICON_FORWARD_16("Forward16.gif"), + ICON_GOTO_24("Goto24.gif"), ICON_GREEN_CIRCLE_16("GreenCircle16.gif"), ICON_HELP_16("Help16.gif"), ICON_HISTORY_16("History16.gif"), ICON_IMPORT_16("Import16.gif"), ICON_INFORMATION_16("Information16.png"), + ICON_LAST_16("Last16.gif"), ICON_LAUNCH_24("LaunchRed24.png"), ICON_LAUNCH_PLUS_24("LaunchRedPlus24.png"), ICON_MAGNIFY_16("Magnify16.png"), diff --git a/src/org/infinity/icon/Last16.gif b/src/org/infinity/icon/Last16.gif new file mode 100644 index 000000000..f41196f9a Binary files /dev/null and b/src/org/infinity/icon/Last16.gif differ diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 9724c2e30..a47a722d1 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -118,6 +118,10 @@ public class BamResource implements Resource, Closeable, Writeable, Referenceabl private static final ButtonPanel.Control CTRL_FRAME_LABEL = ButtonPanel.Control.CUSTOM_7; private static final ButtonPanel.Control PROPERTIES = ButtonPanel.Control.CUSTOM_8; private static final ButtonPanel.Control BAM_EDIT = ButtonPanel.Control.CUSTOM_9; + private static final ButtonPanel.Control CTRL_FIRST_CYCLE = ButtonPanel.Control.CUSTOM_10; + private static final ButtonPanel.Control CTRL_LAST_CYCLE = ButtonPanel.Control.CUSTOM_11; + private static final ButtonPanel.Control CTRL_FIRST_FRAME = ButtonPanel.Control.CUSTOM_12; + private static final ButtonPanel.Control CTRL_LAST_FRAME = ButtonPanel.Control.CUSTOM_13; private static boolean transparencyEnabled = true; @@ -200,26 +204,15 @@ public void searchReferences(Component parent) { @Override public void actionPerformed(ActionEvent event) { - if (buttonControlPanel.getControlByType(CTRL_PREV_CYCLE) == event.getSource()) { - curCycle--; - bamControl.setSharedPerCycle(curCycle >= 0); - bamControl.cycleSet(curCycle); - updateCanvasSize(); - if (timer != null && timer.isRunning() && bamControl.cycleFrameCount() == 0) { - timer.stop(); - ((JToggleButton) buttonControlPanel.getControlByType(CTRL_PLAY)).setSelected(false); - } - curFrame = 0; - showFrame(); + if (buttonControlPanel.getControlByType(CTRL_FIRST_CYCLE) == event.getSource()) { + setCycleIndex(0); + } else if (buttonControlPanel.getControlByType(CTRL_PREV_CYCLE) == event.getSource()) { + setCycleIndex(curCycle - 1); } else if (buttonControlPanel.getControlByType(CTRL_NEXT_CYCLE) == event.getSource()) { - curCycle++; - bamControl.setSharedPerCycle(curCycle >= 0); - bamControl.cycleSet(curCycle); - updateCanvasSize(); - if (timer != null && timer.isRunning() && bamControl.cycleFrameCount() == 0) { - timer.stop(); - ((JToggleButton) buttonControlPanel.getControlByType(CTRL_PLAY)).setSelected(false); - } + setCycleIndex(curCycle + 1); + } else if (buttonControlPanel.getControlByType(CTRL_LAST_CYCLE) == event.getSource()) { + setCycleIndex(bamControl.cycleCount() - 1); + } else if (buttonControlPanel.getControlByType(CTRL_FIRST_FRAME) == event.getSource()) { curFrame = 0; showFrame(); } else if (buttonControlPanel.getControlByType(CTRL_PREV_FRAME) == event.getSource()) { @@ -228,6 +221,13 @@ public void actionPerformed(ActionEvent event) { } else if (buttonControlPanel.getControlByType(CTRL_NEXT_FRAME) == event.getSource()) { curFrame++; showFrame(); + } else if (buttonControlPanel.getControlByType(CTRL_LAST_FRAME) == event.getSource()) { + if (curCycle >= 0) { + curFrame = bamControl.cycleFrameCount() - 1; + } else { + curFrame = decoder.frameCount() - 1; + } + showFrame(); } else if (buttonControlPanel.getControlByType(CTRL_PLAY) == event.getSource()) { if (((JToggleButton) buttonControlPanel.getControlByType(CTRL_PLAY)).isSelected()) { if (timer == null) { @@ -469,20 +469,32 @@ public JComponent makeViewer(ViewableContainer container) { bPlay.addActionListener(this); JLabel lCycle = new JLabel("", SwingConstants.CENTER); + JButton bFirstCycle = new JButton(Icons.ICON_FIRST_16.getIcon()); + bFirstCycle.setMargin(new Insets(bFirstCycle.getMargin().top, 2, bFirstCycle.getMargin().bottom, 2)); + bFirstCycle.addActionListener(this); JButton bPrevCycle = new JButton(Icons.ICON_BACK_16.getIcon()); bPrevCycle.setMargin(new Insets(bPrevCycle.getMargin().top, 2, bPrevCycle.getMargin().bottom, 2)); bPrevCycle.addActionListener(this); JButton bNextCycle = new JButton(Icons.ICON_FORWARD_16.getIcon()); bNextCycle.setMargin(bPrevCycle.getMargin()); bNextCycle.addActionListener(this); + JButton bLastCycle = new JButton(Icons.ICON_LAST_16.getIcon()); + bLastCycle.setMargin(new Insets(bLastCycle.getMargin().top, 2, bLastCycle.getMargin().bottom, 2)); + bLastCycle.addActionListener(this); JLabel lFrame = new JLabel("", SwingConstants.CENTER); + JButton bFirstFrame = new JButton(Icons.ICON_FIRST_16.getIcon()); + bFirstFrame.setMargin(bPrevCycle.getMargin()); + bFirstFrame.addActionListener(this); JButton bPrevFrame = new JButton(Icons.ICON_BACK_16.getIcon()); bPrevFrame.setMargin(bPrevCycle.getMargin()); bPrevFrame.addActionListener(this); JButton bNextFrame = new JButton(Icons.ICON_FORWARD_16.getIcon()); bNextFrame.setMargin(bPrevCycle.getMargin()); bNextFrame.addActionListener(this); + JButton bLastFrame = new JButton(Icons.ICON_LAST_16.getIcon()); + bLastFrame.setMargin(bPrevCycle.getMargin()); + bLastFrame.addActionListener(this); cbTransparency = new JCheckBox("Enable transparency", transparencyEnabled); if (decoder != null) { @@ -496,11 +508,17 @@ public JComponent makeViewer(ViewableContainer container) { optionsPanel.add(cbTransparency); buttonControlPanel.addControl(lCycle, CTRL_CYCLE_LABEL); + buttonControlPanel.addControl(bFirstCycle, CTRL_FIRST_CYCLE); buttonControlPanel.addControl(bPrevCycle, CTRL_PREV_CYCLE); buttonControlPanel.addControl(bNextCycle, CTRL_NEXT_CYCLE); + buttonControlPanel.addControl(bLastCycle, CTRL_LAST_CYCLE); + buttonControlPanel.addControl(new JPanel()); buttonControlPanel.addControl(lFrame, CTRL_FRAME_LABEL); + buttonControlPanel.addControl(bFirstFrame, CTRL_FIRST_FRAME); buttonControlPanel.addControl(bPrevFrame, CTRL_PREV_FRAME); buttonControlPanel.addControl(bNextFrame, CTRL_NEXT_FRAME); + buttonControlPanel.addControl(bLastFrame, CTRL_LAST_FRAME); + buttonControlPanel.addControl(new JPanel()); buttonControlPanel.addControl(bPlay, CTRL_PLAY); buttonControlPanel.add(optionsPanel); buttonControlPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 4, 0)); @@ -777,27 +795,54 @@ private void showFrame() { .setText("Frame: " + curFrame + "/" + (decoder.frameCount() - 1)); } + buttonControlPanel.getControlByType(CTRL_FIRST_CYCLE).setEnabled(curCycle != 0); buttonControlPanel.getControlByType(CTRL_PREV_CYCLE).setEnabled(curCycle > -1); buttonControlPanel.getControlByType(CTRL_NEXT_CYCLE).setEnabled(curCycle + 1 < bamControl.cycleCount()); + buttonControlPanel.getControlByType(CTRL_LAST_CYCLE).setEnabled(curCycle + 1 != bamControl.cycleCount()); + buttonControlPanel.getControlByType(CTRL_FIRST_FRAME).setEnabled(curFrame != 0); buttonControlPanel.getControlByType(CTRL_PREV_FRAME).setEnabled(curFrame > 0); if (curCycle >= 0) { buttonControlPanel.getControlByType(CTRL_NEXT_FRAME).setEnabled(curFrame + 1 < bamControl.cycleFrameCount()); + buttonControlPanel.getControlByType(CTRL_LAST_FRAME).setEnabled(curFrame + 1 != bamControl.cycleFrameCount()); buttonControlPanel.getControlByType(CTRL_PLAY).setEnabled(bamControl.cycleFrameCount() > 1); } else { buttonControlPanel.getControlByType(CTRL_NEXT_FRAME).setEnabled(curFrame + 1 < decoder.frameCount()); + buttonControlPanel.getControlByType(CTRL_LAST_FRAME).setEnabled(curFrame + 1 != decoder.frameCount()); buttonControlPanel.getControlByType(CTRL_PLAY).setEnabled(decoder.frameCount() > 1); } } else { buttonControlPanel.getControlByType(CTRL_PLAY).setEnabled(false); + buttonControlPanel.getControlByType(CTRL_FIRST_CYCLE).setEnabled(false); buttonControlPanel.getControlByType(CTRL_PREV_CYCLE).setEnabled(false); buttonControlPanel.getControlByType(CTRL_NEXT_CYCLE).setEnabled(false); + buttonControlPanel.getControlByType(CTRL_LAST_CYCLE).setEnabled(false); + buttonControlPanel.getControlByType(CTRL_FIRST_FRAME).setEnabled(false); buttonControlPanel.getControlByType(CTRL_PREV_FRAME).setEnabled(false); buttonControlPanel.getControlByType(CTRL_NEXT_FRAME).setEnabled(false); + buttonControlPanel.getControlByType(CTRL_LAST_FRAME).setEnabled(false); } } } + /** + * Sets the specified BAM cycle. + * + * @param cycleIdx Cycle index. Negative values are allows and indicate to show all available frames. + */ + private void setCycleIndex(int cycleIdx) { + curCycle = cycleIdx; + bamControl.setSharedPerCycle(curCycle >= 0); + bamControl.cycleSet(curCycle); + updateCanvasSize(); + if (timer != null && timer.isRunning() && bamControl.cycleFrameCount() == 0) { + timer.stop(); + ((JToggleButton) buttonControlPanel.getControlByType(CTRL_PLAY)).setSelected(false); + } + curFrame = 0; + showFrame(); + } + /** * Exports each frame of the BamDecoder data. *