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}