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.CanvasActions; 022import icy.canvas.CanvasLayerEvent; 023import icy.canvas.CanvasLayerListener; 024import icy.canvas.IcyCanvas; 025import icy.canvas.Layer; 026import icy.gui.component.IcyTextField; 027import icy.gui.component.IcyTextField.TextChangeListener; 028import icy.gui.component.editor.VisibleCellEditor; 029import icy.gui.component.renderer.VisibleCellRenderer; 030import icy.gui.main.ActiveViewerListener; 031import icy.gui.viewer.Viewer; 032import icy.gui.viewer.ViewerEvent; 033import icy.gui.viewer.ViewerEvent.ViewerEventType; 034import icy.system.thread.ThreadUtil; 035import icy.util.StringUtil; 036 037import java.awt.BorderLayout; 038import java.awt.event.KeyEvent; 039import java.util.ArrayList; 040import java.util.List; 041 042import javax.swing.ActionMap; 043import javax.swing.InputMap; 044import javax.swing.JComponent; 045import javax.swing.JPanel; 046import javax.swing.JScrollPane; 047import javax.swing.KeyStroke; 048import javax.swing.ListSelectionModel; 049import javax.swing.ScrollPaneConstants; 050import javax.swing.event.ListSelectionEvent; 051import javax.swing.event.ListSelectionListener; 052import javax.swing.table.AbstractTableModel; 053 054import org.jdesktop.swingx.JXTable; 055import org.jdesktop.swingx.decorator.HighlighterFactory; 056import org.jdesktop.swingx.table.ColumnControlButton; 057import org.jdesktop.swingx.table.TableColumnExt; 058 059/** 060 * @author Stephane 061 */ 062public class LayersPanel extends JPanel implements ActiveViewerListener, CanvasLayerListener, TextChangeListener, 063 ListSelectionListener 064{ 065 private class CanvasRefresher implements Runnable 066 { 067 IcyCanvas newCanvas; 068 069 public CanvasRefresher() 070 { 071 super(); 072 } 073 074 @Override 075 public void run() 076 { 077 final IcyCanvas c = newCanvas; 078 079 // change canvas 080 if (canvas != c) 081 { 082 if (canvas != null) 083 canvas.removeLayerListener(LayersPanel.this); 084 085 canvas = c; 086 087 if (canvas != null) 088 canvas.addLayerListener(LayersPanel.this); 089 } 090 091 refreshLayersInternal(); 092 } 093 } 094 095 /** 096 * 097 */ 098 private static final long serialVersionUID = 4550426171735455449L; 099 100 static final String[] columnNames = {"Name", ""}; 101 102 List<Layer> layers; 103 IcyCanvas canvas; 104 105 // GUI 106 AbstractTableModel tableModel; 107 ListSelectionModel tableSelectionModel; 108 JXTable table; 109 IcyTextField nameFilter; 110 LayerControlPanel controlPanel; 111 112 // internals 113 boolean isSelectionAdjusting; 114 boolean isLayerEditing; 115 boolean isLayerPropertiesAdjusting; 116 117 final Runnable layersRefresher; 118 final Runnable tableDataRefresher; 119 final Runnable controlPanelRefresher; 120 final CanvasRefresher canvasRefresher; 121 122 public LayersPanel() 123 { 124 super(); 125 126 layers = new ArrayList<Layer>(); 127 canvas = null; 128 isSelectionAdjusting = false; 129 isLayerEditing = false; 130 isLayerPropertiesAdjusting = false; 131 132 layersRefresher = new Runnable() 133 { 134 @Override 135 public void run() 136 { 137 refreshLayersInternal(); 138 } 139 }; 140 tableDataRefresher = new Runnable() 141 { 142 @Override 143 public void run() 144 { 145 refreshTableData(); 146 } 147 }; 148 controlPanelRefresher = new Runnable() 149 { 150 @Override 151 public void run() 152 { 153 controlPanel.refresh(); 154 } 155 }; 156 canvasRefresher = new CanvasRefresher(); 157 158 // build GUI 159 initialize(); 160 161 // build table 162 tableModel = new AbstractTableModel() 163 { 164 /** 165 * 166 */ 167 private static final long serialVersionUID = -8573364273165723214L; 168 169 @Override 170 public int getColumnCount() 171 { 172 return columnNames.length; 173 } 174 175 @Override 176 public String getColumnName(int column) 177 { 178 return columnNames[column]; 179 } 180 181 @Override 182 public int getRowCount() 183 { 184 return layers.size(); 185 } 186 187 @Override 188 public Object getValueAt(int row, int column) 189 { 190 // safe 191 if (row >= layers.size()) 192 return null; 193 194 final Layer layer = layers.get(row); 195 196 switch (column) 197 { 198 case 0: 199 // layer name 200 return layer.getName(); 201 202 case 1: 203 // layer visibility 204 return Boolean.valueOf(layer.isVisible()); 205 206 default: 207 return ""; 208 } 209 } 210 211 @Override 212 public void setValueAt(Object value, int row, int column) 213 { 214 // safe 215 if (row >= layers.size()) 216 return; 217 218 isLayerEditing = true; 219 try 220 { 221 final Layer layer = layers.get(row); 222 223 switch (column) 224 { 225 case 0: 226 layer.setName((String) value); 227 break; 228 229 case 1: 230 // layer visibility 231 layer.setVisible(((Boolean) value).booleanValue()); 232 break; 233 } 234 } 235 finally 236 { 237 isLayerEditing = false; 238 } 239 } 240 241 @Override 242 public boolean isCellEditable(int row, int column) 243 { 244 // safe 245 if (row >= layers.size()) 246 return false; 247 248 final boolean editable; 249 250 // name field ? 251 if (column == 0) 252 { 253 final Layer layer = layers.get(row); 254 editable = (layer != null) ? !layer.isReadOnly() : false; 255 } 256 else 257 editable = true; 258 259 return editable; 260 } 261 262 @Override 263 public Class<?> getColumnClass(int columnIndex) 264 { 265 switch (columnIndex) 266 { 267 default: 268 case 0: 269 // layer name 270 return String.class; 271 272 case 1: 273 // layer visibility 274 return Boolean.class; 275 } 276 } 277 }; 278 // set table model 279 table.setModel(tableModel); 280 // alternate highlight 281 table.setHighlighters(HighlighterFactory.createSimpleStriping()); 282 // disable extra actions from column control 283 ((ColumnControlButton) table.getColumnControl()).setAdditionalActionsVisible(false); 284 // remove the internal find command (we have our own filter) 285 table.getActionMap().remove("find"); 286 287 TableColumnExt col; 288 289 // columns setting - name 290 col = table.getColumnExt(0); 291 col.setPreferredWidth(140); 292 col.setToolTipText("Layer name (double click in a cell to edit)"); 293 294 // columns setting - visible 295 col = table.getColumnExt(1); 296 col.setPreferredWidth(20); 297 col.setMinWidth(20); 298 col.setMaxWidth(20); 299 col.setCellEditor(new VisibleCellEditor(18)); 300 col.setCellRenderer(new VisibleCellRenderer(18)); 301 col.setToolTipText("Make the layer visible or not"); 302 col.setResizable(false); 303 304 // table selection model 305 tableSelectionModel = table.getSelectionModel(); 306 tableSelectionModel.addListSelectionListener(this); 307 tableSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 308 309 // create shortcuts 310 buildActionMap(); 311 312 // and refresh layers 313 refreshLayers(); 314 } 315 316 private void initialize() 317 { 318 nameFilter = new IcyTextField(); 319 nameFilter.setToolTipText("Enter a string sequence to filter Layer on name"); 320 nameFilter.addTextChangeListener(this); 321 322 table = new JXTable(); 323 table.setAutoStartEditOnKeyStroke(false); 324 table.setRowHeight(24); 325 table.setShowVerticalLines(false); 326 table.setColumnControlVisible(true); 327 table.setColumnSelectionAllowed(false); 328 table.setRowSelectionAllowed(true); 329 table.setAutoCreateRowSorter(true); 330 331 controlPanel = new LayerControlPanel(this); 332 333 setLayout(new BorderLayout(0, 0)); 334 add(nameFilter, BorderLayout.NORTH); 335 add(new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 336 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); 337 add(controlPanel, BorderLayout.SOUTH); 338 339 validate(); 340 } 341 342 void buildActionMap() 343 { 344 final InputMap imap = table.getInputMap(JComponent.WHEN_FOCUSED); 345 final ActionMap amap = table.getActionMap(); 346 347 imap.put(CanvasActions.unselectAction.getKeyStroke(), CanvasActions.unselectAction.getName()); 348 imap.put(CanvasActions.deleteLayersAction.getKeyStroke(), CanvasActions.deleteLayersAction.getName()); 349 // also allow backspace key for delete operation here 350 imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), CanvasActions.deleteLayersAction.getName()); 351 352 // disable search feature (we have our own filter) 353 amap.remove("find"); 354 amap.put(CanvasActions.unselectAction.getName(), CanvasActions.unselectAction); 355 amap.put(CanvasActions.deleteLayersAction.getName(), CanvasActions.deleteLayersAction); 356 } 357 358 public void setNameFilter(String name) 359 { 360 nameFilter.setText(name); 361 } 362 363 /** 364 * refresh Layer list (and refresh table data according) 365 */ 366 protected void refreshLayers() 367 { 368 ThreadUtil.runSingle(layersRefresher); 369 } 370 371 /** 372 * refresh layer list (internal) 373 */ 374 void refreshLayersInternal() 375 { 376 if (canvas != null) 377 layers = filterList(canvas.getLayers(false), nameFilter.getText()); 378 else 379 layers.clear(); 380 381 // refresh table data 382 ThreadUtil.runSingle(tableDataRefresher); 383 } 384 385 /** 386 * Return index of specified Layer in the Layer list 387 */ 388 protected int getLayerIndex(Layer layer) 389 { 390 return layers.indexOf(layer); 391 } 392 393 /** 394 * Return index of specified Layer in the model 395 */ 396 protected int getLayerModelIndex(Layer layer) 397 { 398 return getLayerIndex(layer); 399 } 400 401 /** 402 * Return index of specified Layer in the table 403 */ 404 protected int getLayerTableIndex(Layer layer) 405 { 406 final int ind = getLayerModelIndex(layer); 407 408 if (ind == -1) 409 return ind; 410 411 try 412 { 413 return table.convertRowIndexToView(ind); 414 } 415 catch (IndexOutOfBoundsException e) 416 { 417 return -1; 418 } 419 } 420 421 // public Layer getFirstSelectedRoi() 422 // { 423 // int index = table.getSelectedRow(); 424 // 425 // if (index != -1) 426 // { 427 // try 428 // { 429 // index = table.convertRowIndexToModel(index); 430 // } 431 // catch (IndexOutOfBoundsException e) 432 // { 433 // // ignore 434 // } 435 // 436 // if ((index >= 0) || (index < layers.size())) 437 // return layers.get(index); 438 // } 439 // 440 // return null; 441 // } 442 443 public ArrayList<Layer> getSelectedLayers() 444 { 445 final ArrayList<Layer> result = new ArrayList<Layer>(); 446 447 for (int rowIndex : table.getSelectedRows()) 448 { 449 int index = -1; 450 451 if (rowIndex != -1) 452 { 453 try 454 { 455 index = table.convertRowIndexToModel(rowIndex); 456 } 457 catch (IndexOutOfBoundsException e) 458 { 459 // ignore 460 } 461 } 462 463 if ((index >= 0) && (index < layers.size())) 464 result.add(layers.get(index)); 465 } 466 467 return result; 468 } 469 470 public void clearSelected() 471 { 472 setSelectedLayersInternal(new ArrayList<Layer>()); 473 } 474 475 void setSelectedLayersInternal(List<Layer> newSelected) 476 { 477 isSelectionAdjusting = true; 478 try 479 { 480 table.clearSelection(); 481 482 if (newSelected != null) 483 { 484 for (Layer layer : newSelected) 485 { 486 final int index = getLayerTableIndex(layer); 487 488 if (index > -1) 489 tableSelectionModel.addSelectionInterval(index, index); 490 } 491 } 492 } 493 finally 494 { 495 isSelectionAdjusting = false; 496 } 497 498 // notify selection changed 499 selectionChanged(); 500 } 501 502 List<Layer> filterList(List<Layer> list, String nameFilterText) 503 { 504 final List<Layer> result = new ArrayList<Layer>(); 505 506 final boolean nameEmpty = StringUtil.isEmpty(nameFilterText, true); 507 final String nameFilterUp; 508 509 if (!nameEmpty) 510 nameFilterUp = nameFilterText.trim().toLowerCase(); 511 else 512 nameFilterUp = ""; 513 514 for (Layer layer : list) 515 { 516 // search in name and type 517 if (nameEmpty || (layer.getName().toLowerCase().indexOf(nameFilterUp) != -1)) 518 result.add(layer); 519 } 520 521 return result; 522 } 523 524 protected void refreshTableData() 525 { 526 final List<Layer> save = getSelectedLayers(); 527 528 // need to be done on EDT 529 ThreadUtil.invokeNow(new Runnable() 530 { 531 @Override 532 public void run() 533 { 534 isSelectionAdjusting = true; 535 try 536 { 537 tableModel.fireTableDataChanged(); 538 } 539 finally 540 { 541 isSelectionAdjusting = false; 542 } 543 544 setSelectedLayersInternal(save); 545 } 546 }); 547 } 548 549 // protected void refreshTableRow(final Layer layer) 550 // { 551 // isSelectionAdjusting = true; 552 // try 553 // { 554 // final int rowIndex = getLayerModelIndex(layer); 555 // 556 // tableModel.fireTableRowsUpdated(rowIndex, rowIndex); 557 // } 558 // finally 559 // { 560 // isSelectionAdjusting = false; 561 // } 562 // 563 // // restore selected layer 564 // if (sequence != null) 565 // setSelectedLayersInternal(sequence.getSelectedROIs()); 566 // else 567 // setSelectedLayersInternal(null); 568 // 569 // // refresh control panel 570 // refreshControlPanel(); 571 // } 572 573 /** 574 * Called when selection changed 575 */ 576 protected void selectionChanged() 577 { 578 // refresh control panel 579 ThreadUtil.runSingle(controlPanelRefresher); 580 } 581 582 @Override 583 public void textChanged(IcyTextField source, boolean validate) 584 { 585 if (source == nameFilter) 586 refreshLayers(); 587 } 588 589 @Override 590 public void valueChanged(ListSelectionEvent e) 591 { 592 // internal change --> ignore 593 if (isSelectionAdjusting || e.getValueIsAdjusting()) 594 return; 595 596 selectionChanged(); 597 } 598 599 @Override 600 public void viewerActivated(Viewer viewer) 601 { 602 if (viewer != null) 603 canvasRefresher.newCanvas = viewer.getCanvas(); 604 else 605 canvasRefresher.newCanvas = null; 606 607 ThreadUtil.runSingle(canvasRefresher); 608 } 609 610 @Override 611 public void viewerDeactivated(Viewer viewer) 612 { 613 // nothing here 614 } 615 616 @Override 617 public void activeViewerChanged(ViewerEvent event) 618 { 619 if (event.getType() == ViewerEventType.CANVAS_CHANGED) 620 { 621 canvasRefresher.newCanvas = event.getSource().getCanvas(); 622 ThreadUtil.runSingle(canvasRefresher); 623 } 624 } 625 626 @Override 627 public void canvasLayerChanged(CanvasLayerEvent event) 628 { 629 // refresh layer from externals changes 630 if (isLayerEditing) 631 return; 632 633 switch (event.getType()) 634 { 635 case ADDED: 636 case REMOVED: 637 refreshLayers(); 638 break; 639 640 case CHANGED: 641 final String property = event.getProperty(); 642 643 if (Layer.PROPERTY_NAME.equals(property) || Layer.PROPERTY_OPACITY.equals(property) 644 || Layer.PROPERTY_VISIBLE.equals(property)) 645 // refresh table data 646 ThreadUtil.runSingle(tableDataRefresher); 647 break; 648 } 649 } 650 651}