001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.inspector;
020
021import icy.action.SequenceOperationActions;
022import icy.gui.component.button.IcyButton;
023import icy.gui.main.ActiveSequenceListener;
024import icy.main.Icy;
025import icy.preferences.GeneralPreferences;
026import icy.sequence.Sequence;
027import icy.sequence.SequenceEvent;
028import icy.system.thread.ThreadUtil;
029import icy.undo.AbstractIcyUndoableEdit;
030import icy.undo.IcyUndoManager;
031import icy.undo.IcyUndoManagerListener;
032
033import java.awt.BorderLayout;
034import java.awt.Component;
035
036import javax.swing.Box;
037import javax.swing.BoxLayout;
038import javax.swing.Icon;
039import javax.swing.JLabel;
040import javax.swing.JPanel;
041import javax.swing.JScrollPane;
042import javax.swing.JSpinner;
043import javax.swing.JTable;
044import javax.swing.ListSelectionModel;
045import javax.swing.ScrollPaneConstants;
046import javax.swing.SpinnerNumberModel;
047import javax.swing.border.EmptyBorder;
048import javax.swing.event.ChangeEvent;
049import javax.swing.event.ChangeListener;
050import javax.swing.event.ListSelectionEvent;
051import javax.swing.event.ListSelectionListener;
052import javax.swing.table.AbstractTableModel;
053import javax.swing.table.TableColumn;
054import javax.swing.table.TableColumnModel;
055
056/**
057 * @author Stephane
058 */
059public class UndoManagerPanel extends JPanel implements ActiveSequenceListener, ListSelectionListener,
060        IcyUndoManagerListener, ChangeListener
061{
062    /**
063     * 
064     */
065    private static final long serialVersionUID = 1464754827529975860L;
066
067    static final String[] columnNames = {"", "Action"};
068
069    protected IcyUndoManager undoManager;
070
071    // GUI
072    AbstractTableModel tableModel;
073    ListSelectionModel tableSelectionModel;
074    JTable table;
075
076    // internals
077    boolean isSelectionAdjusting;
078    IcyButton undoButton;
079    IcyButton redoButton;
080    JSpinner historySizeField;
081    IcyButton clearAllButLastButton;
082    IcyButton clearAllButton;
083    final Runnable refresher;
084
085    public UndoManagerPanel()
086    {
087        super();
088
089        undoManager = null;
090        isSelectionAdjusting = false;
091
092        initialize();
093
094        historySizeField.setValue(Integer.valueOf(GeneralPreferences.getHistorySize()));
095        historySizeField.addChangeListener(this);
096
097        refresher = new Runnable()
098        {
099            @Override
100            public void run()
101            {
102                refreshTableDataAndActions();
103            }
104        };
105
106        refresher.run();
107    }
108
109    private void initialize()
110    {
111        // build table
112        tableModel = new AbstractTableModel()
113        {
114            /**
115             * 
116             */
117            private static final long serialVersionUID = -8573364273165723214L;
118
119            @Override
120            public int getColumnCount()
121            {
122                return columnNames.length;
123            }
124
125            @Override
126            public String getColumnName(int column)
127            {
128                return columnNames[column];
129            }
130
131            @Override
132            public int getRowCount()
133            {
134                if (undoManager != null)
135                    return undoManager.getEditsCount() + 1;
136
137                return 1;
138            }
139
140            @Override
141            public Object getValueAt(int row, int column)
142            {
143                if (row == 0)
144                {
145                    if (column == 0)
146                        return null;
147
148                    if (undoManager != null)
149                        return "Initial state";
150
151                    return "No opened sequence";
152                }
153
154                if (undoManager != null)
155                {
156                    final AbstractIcyUndoableEdit edit = undoManager.getEdit(row - 1);
157
158                    switch (column)
159                    {
160                        case 0:
161                            return edit.getIcon();
162
163                        case 1:
164                            return edit.getPresentationName();
165                    }
166                }
167
168                return "";
169            }
170
171            @Override
172            public boolean isCellEditable(int row, int column)
173            {
174                return false;
175            }
176
177            @Override
178            public Class<?> getColumnClass(int columnIndex)
179            {
180                if (columnIndex == 0)
181                    return Icon.class;
182
183                return String.class;
184            }
185        };
186
187        table = new JTable(tableModel);
188        table.setToolTipText("Click on an action to undo or redo until that point");
189
190        final TableColumnModel colModel = table.getColumnModel();
191        TableColumn col;
192
193        // columns setting
194        col = colModel.getColumn(0);
195        col.setPreferredWidth(20);
196        col.setMinWidth(20);
197        col.setMaxWidth(20);
198
199        col = colModel.getColumn(1);
200        col.setPreferredWidth(100);
201        col.setMinWidth(60);
202
203        table.setRowHeight(20);
204        table.setColumnSelectionAllowed(false);
205        table.setRowSelectionAllowed(true);
206        table.setShowVerticalLines(true);
207        table.setAutoCreateRowSorter(false);
208        table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
209
210        tableSelectionModel = table.getSelectionModel();
211        tableSelectionModel.addListSelectionListener(this);
212        tableSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
213
214        final JPanel middlePanel = new JPanel();
215        middlePanel.setLayout(new BoxLayout(middlePanel, BoxLayout.PAGE_AXIS));
216
217        middlePanel.add(table.getTableHeader());
218        final JScrollPane sc = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
219                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
220        sc.setToolTipText("");
221        middlePanel.add(sc);
222
223        final JPanel bottomPanel = new JPanel();
224        bottomPanel.setBorder(new EmptyBorder(2, 0, 0, 0));
225        bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
226
227        undoButton = new IcyButton(SequenceOperationActions.undoAction);
228        undoButton.setFlat(true);
229        undoButton.setHideActionText(true);
230        bottomPanel.add(undoButton);
231
232        redoButton = new IcyButton(SequenceOperationActions.redoAction);
233        redoButton.setFlat(true);
234        redoButton.setHideActionText(true);
235        bottomPanel.add(redoButton);
236
237        Component horizontalGlue = Box.createHorizontalGlue();
238        bottomPanel.add(horizontalGlue);
239
240        JLabel lblNewLabel = new JLabel("History size");
241        lblNewLabel.setToolTipText("");
242        bottomPanel.add(lblNewLabel);
243
244        Component horizontalStrut = Box.createHorizontalStrut(8);
245        bottomPanel.add(horizontalStrut);
246
247        historySizeField = new JSpinner();
248        historySizeField.setModel(new SpinnerNumberModel(50, 1, 200, 1));
249        historySizeField.setToolTipText("Maximum size of the history (lower value will reduce memory usage)");
250        bottomPanel.add(historySizeField);
251
252        Component horizontalStrut_2 = Box.createHorizontalStrut(8);
253        bottomPanel.add(horizontalStrut_2);
254
255        clearAllButLastButton = new IcyButton(SequenceOperationActions.undoClearAllButLastAction);
256        clearAllButLastButton.setFlat(true);
257        clearAllButLastButton.setHideActionText(true);
258        bottomPanel.add(clearAllButLastButton);
259
260        clearAllButton = new IcyButton(SequenceOperationActions.undoClearAction);
261        clearAllButton.setFlat(true);
262        clearAllButton.setHideActionText(true);
263        bottomPanel.add(clearAllButton);
264
265        setLayout(new BorderLayout());
266
267        add(middlePanel, BorderLayout.CENTER);
268        add(bottomPanel, BorderLayout.SOUTH);
269    }
270
271    public void setUndoManager(IcyUndoManager value)
272    {
273        if (undoManager != value)
274        {
275            if (undoManager != null)
276                undoManager.removeListener(this);
277
278            undoManager = value;
279
280            if (undoManager != null)
281                undoManager.addListener(this);
282
283            // refresh data and actions
284            ThreadUtil.bgRunSingle(refresher);
285        }
286    }
287
288    // /**
289    // * Return index of specified Edit
290    // */
291    // protected int getEditIndex(AbstractIcyUndoableEdit edit)
292    // {
293    // if (undoManager != null)
294    // return undoManager.getSignificantIndex(edit);
295    //
296    // return -1;
297    // }
298
299    public AbstractIcyUndoableEdit getLastSelectedEdit()
300    {
301        if (undoManager != null)
302        {
303            final int index = tableSelectionModel.getMaxSelectionIndex();
304
305            if (index > 0)
306                return undoManager.getSignificantEdit(index - 1);
307        }
308
309        return null;
310    }
311
312    protected void refreshTableDataAndActions()
313    {
314        ThreadUtil.invokeNow(new Runnable()
315        {
316            @Override
317            public void run()
318            {
319                isSelectionAdjusting = true;
320                try
321                {
322                    tableModel.fireTableDataChanged();
323
324                    if (undoManager != null)
325                        tableSelectionModel.setSelectionInterval(0, undoManager.getNextAddIndex());
326                    else
327                        tableSelectionModel.setSelectionInterval(0, 0);
328                }
329                finally
330                {
331                    isSelectionAdjusting = false;
332                }
333
334                if (undoManager != null)
335                {
336                    undoButton.setEnabled(undoManager.canUndo());
337                    redoButton.setEnabled(undoManager.canRedo());
338                    clearAllButLastButton.setEnabled(undoManager.canUndo());
339                    clearAllButton.setEnabled(undoManager.canUndo() || undoManager.canRedo());
340                }
341                else
342                {
343                    undoButton.setEnabled(false);
344                    redoButton.setEnabled(false);
345                    clearAllButLastButton.setEnabled(false);
346                    clearAllButton.setEnabled(false);
347                }
348            }
349        });
350    }
351
352    /**
353     * called when selection has changed
354     */
355    protected void selectionChanged()
356    {
357        // process undo / redo operation
358        if (undoManager != null)
359        {
360            final AbstractIcyUndoableEdit selectedEdit = getLastSelectedEdit();
361
362            // first entry
363            if (selectedEdit == null)
364                undoManager.undoAll();
365            else
366                undoManager.undoOrRedoTo(selectedEdit);
367        }
368    }
369
370    @Override
371    public void valueChanged(ListSelectionEvent e)
372    {
373        if (e.getValueIsAdjusting() || isSelectionAdjusting)
374            return;
375
376        if (tableSelectionModel.getMinSelectionIndex() != 0)
377            tableSelectionModel.setSelectionInterval(0, tableSelectionModel.getMaxSelectionIndex());
378        else
379            selectionChanged();
380    }
381
382    @Override
383    public void stateChanged(ChangeEvent e)
384    {
385        final int value = ((Integer) historySizeField.getValue()).intValue();
386
387        // change size of all current active undo manager
388        for (Sequence sequence : Icy.getMainInterface().getSequences())
389        {
390            final IcyUndoManager um = sequence.getUndoManager();
391
392            if (um != null)
393                um.setLimit(value);
394        }
395
396        GeneralPreferences.setHistorySize(value);
397
398        refreshTableDataAndActions();
399    }
400
401    @Override
402    public void undoManagerChanged(IcyUndoManager source)
403    {
404        ThreadUtil.bgRunSingle(refresher);
405    }
406
407    @Override
408    public void sequenceActivated(Sequence sequence)
409    {
410        if (sequence == null)
411            setUndoManager(null);
412        else
413            setUndoManager(sequence.getUndoManager());
414    }
415
416    @Override
417    public void sequenceDeactivated(Sequence sequence)
418    {
419        // nothing here
420    }
421
422    @Override
423    public void activeSequenceChanged(SequenceEvent event)
424    {
425        // nothing here
426    }
427}