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.dialog; 020 021import icy.common.exception.UnsupportedFormatException; 022import icy.file.Loader; 023import icy.file.SequenceFileImporter; 024import icy.gui.component.ThumbnailComponent; 025import icy.gui.util.ComponentUtil; 026import icy.image.IcyBufferedImage; 027import icy.image.IcyBufferedImageUtil; 028import icy.main.Icy; 029import icy.resource.ResourceUtil; 030import icy.sequence.MetaDataUtil; 031import icy.sequence.SequenceIdImporter; 032import icy.util.OMEUtil; 033 034import java.awt.BorderLayout; 035import java.awt.Dimension; 036import java.awt.FlowLayout; 037import java.awt.GridLayout; 038import java.awt.event.ActionEvent; 039import java.awt.event.ActionListener; 040import java.awt.event.MouseAdapter; 041import java.awt.event.MouseEvent; 042import java.awt.image.BufferedImage; 043import java.io.IOException; 044import java.util.Timer; 045import java.util.TimerTask; 046 047import javax.swing.Box; 048import javax.swing.JButton; 049import javax.swing.JLabel; 050import javax.swing.JPanel; 051import javax.swing.JScrollPane; 052import javax.swing.ScrollPaneConstants; 053 054import loci.formats.IFormatReader; 055import loci.formats.ome.OMEXMLMetadataImpl; 056import ome.xml.meta.MetadataRetrieve; 057import ome.xml.meta.OMEXMLMetadata; 058 059/** 060 * Dialog used to select which serie to open for multi serie image. 061 * 062 * @author Stephane 063 */ 064public class SeriesSelectionDialog extends ActionDialog implements Runnable 065{ 066 /** 067 * 068 */ 069 private static final long serialVersionUID = -2133845128887305016L; 070 071 protected static final int NUM_COL = 4; 072 protected static final int THUMB_X = 160; 073 protected static final int THUMB_Y = 140; 074 075 // GUI 076 protected JScrollPane scrollPane; 077 protected JPanel gridPanel; 078 protected ThumbnailComponent[] serieComponents; 079 protected JButton selectAllBtn; 080 protected JButton unselectAllBtn; 081 082 // internal 083 protected IFormatReader reader; 084 protected SequenceIdImporter importer; 085 protected String id; 086 protected OMEXMLMetadata metadata; 087 protected boolean singleSelection; 088 protected int[] selectedSeries; 089 protected final MouseAdapter serieDoubleClickAction; 090 protected final ActionListener serieSimpleClickAction; 091 protected final Thread loadingThread; 092 093 /** 094 * @deprecated Use {@link #SeriesSelectionDialog(SequenceFileImporter, String)} instead. 095 */ 096 @Deprecated 097 public SeriesSelectionDialog(IFormatReader reader) 098 { 099 super("Series selection", null, Icy.getMainInterface().getMainFrame()); 100 101 this.reader = reader; 102 // default is empty 103 selectedSeries = new int[] {}; 104 singleSelection = false; 105 106 initialize(); 107 108 serieSimpleClickAction = new ActionListener() 109 { 110 @Override 111 public void actionPerformed(ActionEvent e) 112 { 113 // not used here 114 } 115 }; 116 // double click action = direct selection 117 serieDoubleClickAction = new MouseAdapter() 118 { 119 @Override 120 public void mouseClicked(MouseEvent e) 121 { 122 if (e.getClickCount() == 2) 123 { 124 final ThumbnailComponent thumb = (ThumbnailComponent) e.getSource(); 125 126 for (int i = 0; i < serieComponents.length; i++) 127 { 128 if (serieComponents[i] == thumb) 129 { 130 selectedSeries = new int[] {i}; 131 dispose(); 132 } 133 } 134 } 135 } 136 }; 137 138 final int series; 139 140 if (reader != null) 141 { 142 metadata = OMEUtil.getOMEXMLMetadata((MetadataRetrieve) reader.getMetadataStore()); 143 series = reader.getSeriesCount(); 144 } 145 else 146 { 147 metadata = null; 148 series = 0; 149 } 150 151 serieComponents = new ThumbnailComponent[series]; 152 153 // adjust number of row 154 int numRow = series / NUM_COL; 155 if (series > (NUM_COL * numRow)) 156 numRow++; 157 158 ((GridLayout) gridPanel.getLayout()).setRows(numRow); 159 160 for (int i = 0; i < numRow; i++) 161 { 162 for (int j = 0; j < NUM_COL; j++) 163 { 164 final int index = (i * NUM_COL) + j; 165 166 if (index < series) 167 { 168 final ThumbnailComponent thumb = new ThumbnailComponent(true); 169 170 // add mouse listener (double click action) 171 thumb.addMouseListener(serieDoubleClickAction); 172 173 // remove mouse listener (double click action) 174 if (serieComponents[index] != null) 175 serieComponents[index].removeMouseListener(serieDoubleClickAction); 176 177 serieComponents[index] = thumb; 178 thumb.setEnabled(true); 179 thumb.setTitle("loading..."); 180 thumb.setInfos(""); 181 thumb.setInfos2(""); 182 gridPanel.add(thumb); 183 } 184 else 185 gridPanel.add(Box.createGlue()); 186 } 187 } 188 189 // load thumbnails... 190 loadingThread = new Thread(this, "Series thumbnail loading"); 191 loadingThread.start(); 192 193 // action on "OK" 194 setOkAction(new ActionListener() 195 { 196 @Override 197 public void actionPerformed(ActionEvent e) 198 { 199 int numSelected = 0; 200 for (int i = 0; i < serieComponents.length; i++) 201 if (serieComponents[i].isSelected()) 202 numSelected++; 203 204 selectedSeries = new int[numSelected]; 205 206 int ind = 0; 207 for (int i = 0; i < serieComponents.length; i++) 208 if (serieComponents[i].isSelected()) 209 selectedSeries[ind++] = i; 210 } 211 }); 212 213 // action on "Select All" 214 selectAllBtn.addActionListener(new ActionListener() 215 { 216 @Override 217 public void actionPerformed(ActionEvent e) 218 { 219 for (ThumbnailComponent thumb : serieComponents) 220 thumb.setSelected(true); 221 } 222 }); 223 224 // action on "Unselect All" 225 unselectAllBtn.addActionListener(new ActionListener() 226 { 227 @Override 228 public void actionPerformed(ActionEvent e) 229 { 230 for (ThumbnailComponent thumb : serieComponents) 231 thumb.setSelected(false); 232 } 233 }); 234 235 setPreferredSize(new Dimension(740, 520)); 236 pack(); 237 ComponentUtil.center(this); 238 setVisible(true); 239 } 240 241 public SeriesSelectionDialog(SequenceIdImporter importer, String id, OMEXMLMetadata metadata, 242 boolean singleSelection) 243 { 244 super("Series selection", null, Icy.getMainInterface().getMainFrame()); 245 246 this.importer = importer; 247 this.id = id; 248 this.metadata = metadata; 249 this.singleSelection = singleSelection; 250 // default is empty 251 selectedSeries = new int[] {}; 252 253 initialize(); 254 255 final int series = MetaDataUtil.getNumSeries(metadata); 256 257 // simple click action = simple selection 258 serieSimpleClickAction = new ActionListener() 259 { 260 @Override 261 public void actionPerformed(ActionEvent e) 262 { 263 final Object source = e.getSource(); 264 265 // unselect all others 266 if (SeriesSelectionDialog.this.singleSelection) 267 { 268 for (ThumbnailComponent thumb : serieComponents) 269 { 270 if (thumb != source) 271 thumb.setSelected(false); 272 } 273 } 274 } 275 }; 276 // double click action = direct selection 277 serieDoubleClickAction = new MouseAdapter() 278 { 279 @Override 280 public void mouseClicked(MouseEvent e) 281 { 282 if (e.getClickCount() == 2) 283 { 284 final ThumbnailComponent thumb = (ThumbnailComponent) e.getSource(); 285 286 for (int i = 0; i < serieComponents.length; i++) 287 { 288 if (serieComponents[i] == thumb) 289 { 290 selectedSeries = new int[] {i}; 291 dispose(); 292 } 293 } 294 } 295 } 296 }; 297 298 serieComponents = new ThumbnailComponent[series]; 299 300 // adjust number of row 301 int numRow = series / NUM_COL; 302 if (series > (NUM_COL * numRow)) 303 numRow++; 304 305 ((GridLayout) gridPanel.getLayout()).setRows(numRow); 306 307 for (int i = 0; i < numRow; i++) 308 { 309 for (int j = 0; j < NUM_COL; j++) 310 { 311 final int index = (i * NUM_COL) + j; 312 313 if (index < series) 314 { 315 final ThumbnailComponent thumb = new ThumbnailComponent(true); 316 317 // add mouse listener (double click action) 318 thumb.addMouseListener(serieDoubleClickAction); 319 // single click action 320 thumb.addActionListener(serieSimpleClickAction); 321 322 // remove mouse listener (double click action) 323 if (serieComponents[index] != null) 324 serieComponents[index].removeMouseListener(serieDoubleClickAction); 325 326 serieComponents[index] = thumb; 327 thumb.setEnabled(true); 328 thumb.setTitle("loading..."); 329 thumb.setInfos(""); 330 thumb.setInfos2(""); 331 gridPanel.add(thumb); 332 } 333 else 334 gridPanel.add(Box.createGlue()); 335 } 336 } 337 338 // load thumbnails... 339 loadingThread = new Thread(this, "Series thumbnail loading"); 340 loadingThread.start(); 341 342 // action on "OK" 343 setOkAction(new ActionListener() 344 { 345 @Override 346 public void actionPerformed(ActionEvent e) 347 { 348 int numSelected = 0; 349 for (int i = 0; i < serieComponents.length; i++) 350 if (serieComponents[i].isSelected()) 351 numSelected++; 352 353 selectedSeries = new int[numSelected]; 354 355 int ind = 0; 356 for (int i = 0; i < serieComponents.length; i++) 357 if (serieComponents[i].isSelected()) 358 selectedSeries[ind++] = i; 359 } 360 }); 361 362 // action on "Select All" 363 selectAllBtn.addActionListener(new ActionListener() 364 { 365 @Override 366 public void actionPerformed(ActionEvent e) 367 { 368 for (ThumbnailComponent thumb : serieComponents) 369 thumb.setSelected(true); 370 } 371 }); 372 // action on "Unselect All" 373 unselectAllBtn.addActionListener(new ActionListener() 374 { 375 @Override 376 public void actionPerformed(ActionEvent e) 377 { 378 for (ThumbnailComponent thumb : serieComponents) 379 thumb.setSelected(false); 380 } 381 }); 382 383 // select/unselect all buttons visible only if not in "single selection" mode 384 selectAllBtn.setVisible(!singleSelection); 385 unselectAllBtn.setVisible(!singleSelection); 386 387 setPreferredSize(new Dimension(740, 520)); 388 pack(); 389 ComponentUtil.center(SeriesSelectionDialog.this); 390 setVisible(true); 391 } 392 393 public SeriesSelectionDialog(SequenceFileImporter importer, String id, OMEXMLMetadata metadata, 394 boolean singleSelection) 395 { 396 this((SequenceIdImporter) importer, id, metadata, singleSelection); 397 } 398 399 /** 400 * Create a new dialog to select the series to open from an image. 401 * 402 * @throws IOException 403 * @throws UnsupportedFormatException 404 */ 405 public SeriesSelectionDialog(SequenceFileImporter importer, String id, OMEXMLMetadata metadata) 406 throws UnsupportedFormatException, IOException 407 { 408 this(importer, id, metadata, false); 409 } 410 411 /** 412 * @deprecated Use {@link #SeriesSelectionDialog(SequenceFileImporter, String, OMEXMLMetadata)} instead. 413 */ 414 @Deprecated 415 public SeriesSelectionDialog(SequenceFileImporter importer, String id, OMEXMLMetadataImpl metadata) 416 throws UnsupportedFormatException, IOException 417 { 418 this(importer, id, (OMEXMLMetadata) metadata); 419 } 420 421 /** 422 * Create a new dialog to select the series to open from an image. 423 * 424 * @throws IOException 425 * @throws UnsupportedFormatException 426 */ 427 public SeriesSelectionDialog(SequenceFileImporter importer, String id) 428 throws UnsupportedFormatException, IOException 429 { 430 this(importer, id, Loader.getOMEXMLMetaData(importer, id)); 431 } 432 433 /** 434 * @return the selectedSeries 435 */ 436 public int[] getSelectedSeries() 437 { 438 return selectedSeries; 439 } 440 441 void initialize() 442 { 443 JPanel panel = new JPanel(); 444 getContentPane().add(panel, BorderLayout.NORTH); 445 panel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5)); 446 447 final JLabel lblSelect; 448 if (singleSelection) 449 lblSelect = new JLabel("Select the series and click 'Ok' or directly double click on it to open it."); 450 else 451 lblSelect = new JLabel( 452 "Click on a serie to select / unselect it and click 'Ok' or double click to directly open it."); 453 ComponentUtil.setFontBold(lblSelect); 454 ComponentUtil.setFontSize(lblSelect, 12); 455 panel.add(lblSelect); 456 457 scrollPane = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 458 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 459 mainPanel.add(scrollPane, BorderLayout.CENTER); 460 461 gridPanel = new JPanel(); 462 scrollPane.setViewportView(gridPanel); 463 gridPanel.setLayout(new GridLayout(2, NUM_COL, 0, 0)); 464 465 buttonPanel.removeAll(); 466 467 selectAllBtn = new JButton("Select all"); 468 unselectAllBtn = new JButton("Unselect all"); 469 470 buttonPanel.add(Box.createHorizontalStrut(4)); 471 buttonPanel.add(selectAllBtn); 472 buttonPanel.add(Box.createHorizontalStrut(8)); 473 buttonPanel.add(unselectAllBtn); 474 buttonPanel.add(Box.createHorizontalStrut(8)); 475 buttonPanel.add(Box.createHorizontalGlue()); 476 buttonPanel.add(Box.createHorizontalStrut(8)); 477 buttonPanel.add(okBtn); 478 buttonPanel.add(Box.createHorizontalStrut(8)); 479 buttonPanel.add(cancelBtn); 480 buttonPanel.add(Box.createHorizontalStrut(4)); 481 } 482 483 @Override 484 protected void onClosed() 485 { 486 super.onClosed(); 487 488 // kill loading task in 2 seconds 489 new Timer().schedule(new TimerTask() 490 { 491 @Override 492 public void run() 493 { 494 if ((loadingThread != null) && loadingThread.isAlive()) 495 { 496 try 497 { 498 loadingThread.interrupt(); 499 } 500 catch (Throwable t) 501 { 502 // ignore 503 } 504 } 505 } 506 }, 2000); 507 } 508 509 @Override 510 public void run() 511 { 512 try 513 { 514 // start by filling metadata only... 515 for (int i = 0; i < serieComponents.length; i++) 516 { 517 // interrupt 518 if (isClosed()) 519 return; 520 521 try 522 { 523 final int sizeC = MetaDataUtil.getSizeC(metadata, i); 524 525 serieComponents[i].setTitle(metadata.getImageName(i)); 526 serieComponents[i].setInfos(MetaDataUtil.getSizeX(metadata, i) + " x " 527 + MetaDataUtil.getSizeY(metadata, i) + " - " + MetaDataUtil.getSizeZ(metadata, i) + "Z x " 528 + MetaDataUtil.getSizeT(metadata, i) + "T"); 529 serieComponents[i].setInfos2(sizeC + ((sizeC > 1) ? " channels (" : " channel (") 530 + MetaDataUtil.getDataType(metadata, i) + ")"); 531 } 532 catch (Exception e) 533 { 534 serieComponents[i].setTitle("Cannot read file"); 535 serieComponents[i].setInfos(""); 536 serieComponents[i].setInfos2(""); 537 } 538 539 try 540 { 541 // why does this sometime fails ??? 542 serieComponents[i].setImage(ResourceUtil.ICON_PICTURE); 543 } 544 catch (Exception e) 545 { 546 // ignore 547 } 548 } 549 550 // then try to load thumbnail 551 for (int i = 0; i < serieComponents.length; i++) 552 { 553 // interrupt 554 if (isClosed()) 555 return; 556 557 try 558 { 559 if (importer.open(id, 0)) 560 { 561 try 562 { 563 final IcyBufferedImage img = importer.getThumbnail(i); 564 serieComponents[i] 565 .setImage(IcyBufferedImageUtil.toBufferedImage(img, BufferedImage.TYPE_INT_ARGB)); 566 } 567 finally 568 { 569 importer.close(); 570 } 571 } 572 else 573 serieComponents[i].setImage(ResourceUtil.ICON_DELETE); 574 } 575 catch (OutOfMemoryError e) 576 { 577 // error image, we just totally ignore error here... 578 serieComponents[i].setImage(ResourceUtil.ICON_DELETE); 579 } 580 catch (Exception e) 581 { 582 // error image, we just totally ignore error here... 583 serieComponents[i].setImage(ResourceUtil.ICON_DELETE); 584 } 585 } 586 } 587 catch (ThreadDeath t) 588 { 589 // just stop process... 590 } 591 } 592}