View Javadoc
1   package fr.ifremer.dali.ui.swing.content.observation.photo;
2   
3   /*
4    * #%L
5    * Dali :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import fr.ifremer.dali.dto.DaliBeans;
27  import fr.ifremer.dali.dto.data.photo.PhotoDTO;
28  import fr.ifremer.dali.dto.data.sampling.SamplingOperationDTO;
29  import fr.ifremer.dali.service.DaliTechnicalException;
30  import fr.ifremer.dali.ui.swing.util.image.PhotoViewer;
31  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIHandler;
32  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIModel;
33  import fr.ifremer.quadrige3.core.dao.technical.Images;
34  import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
35  import fr.ifremer.quadrige3.ui.swing.table.editor.ExtendedComboBoxCellEditor;
36  import jaxx.runtime.SwingUtil;
37  import jaxx.runtime.validator.swing.SwingValidator;
38  import org.apache.commons.lang3.StringUtils;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.jdesktop.swingx.table.TableColumnExt;
42  import org.nuiton.jaxx.application.swing.tab.TabHandler;
43  
44  import javax.swing.SwingWorker;
45  import javax.swing.table.TableCellRenderer;
46  import java.util.ArrayList;
47  import java.util.List;
48  import java.util.Objects;
49  import java.util.concurrent.ExecutionException;
50  
51  import static org.nuiton.i18n.I18n.t;
52  
53  /**
54   * Controlleur pour l'onglet photo.
55   */
56  public class PhotosTabUIHandler extends AbstractDaliTableUIHandler<PhotosTableRowModel, PhotosTabUIModel, PhotosTabUI> implements TabHandler {
57  
58      /**
59       * Logger.
60       */
61      private static final Log LOG = LogFactory.getLog(PhotosTabUIHandler.class);
62  
63      private ExtendedComboBoxCellEditor<SamplingOperationDTO> samplingOperationCellEditor;
64  
65      /**
66       * {@inheritDoc}
67       */
68      @Override
69      public void beforeInit(final PhotosTabUI ui) {
70          super.beforeInit(ui);
71  
72          // create model and register to the JAXX context
73          final PhotosTabUIModel model = new PhotosTabUIModel();
74          ui.setContextValue(model);
75      }
76  
77      /**
78       * {@inheritDoc}
79       */
80      @Override
81      public void afterInit(final PhotosTabUI ui) {
82          initUI(ui);
83  
84          // Init viewer
85          SwingUtil.setLayerUI(getUI().getPhotoViewer(), getUI().getPhotoBlockLayer());
86  
87          // init table
88          initTable();
89  
90          // Init listeners
91          initListeners();
92  
93          // Register validator
94          registerValidators(getValidator());
95          listenValidatorValid(getValidator(), getModel());
96  
97          // initial state
98          ui.getDownloadPhotoButton().setEnabled(false);
99          ui.getExportPhotoButton().setEnabled(false);
100     }
101 
102     /**
103      * {@inheritDoc}
104      */
105     @Override
106     protected void onRowModified(int rowIndex, PhotosTableRowModel row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
107         super.onRowModified(rowIndex, row, propertyName, propertyIndex, oldValue, newValue);
108 
109         row.setDirty(true);
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     protected String[] getRowPropertiesToIgnore() {
117         return new String[]{PhotoDTO.PROPERTY_DIRTY};
118     }
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
124     protected boolean isRowValid(PhotosTableRowModel row) {
125 
126         return super.isRowValid(row) && isPhotoValid(row);
127     }
128 
129     private boolean isPhotoValid(PhotosTableRowModel row) {
130 
131         // check name duplicates
132         if (getModel().getRowCount() >= 2) {
133             for (PhotosTableRowModel otherRow : getModel().getRows()) {
134                 if (row == otherRow) continue;
135                 if (StringUtils.isNotBlank(row.getName()) && row.getName().equals(otherRow.getName())) {
136                     // duplicate found
137                     DaliBeans.addError(row, t("dali.photo.name.duplicate"), PhotosTableRowModel.PROPERTY_NAME);
138                 }
139             }
140         }
141 
142         return DaliBeans.hasNoBlockingError(row);
143     }
144 
145     /**
146      * Initialisation du tableau.
147      */
148     private void initTable() {
149 
150         // La colonne mnemonique
151         final TableColumnExt nameCol = addColumn(PhotosTableModel.NAME);
152         nameCol.setSortable(true);
153         nameCol.setMinWidth(100);
154 
155         // La colonne type
156         final TableColumnExt typeCol = addFilterableComboDataColumnToModel(PhotosTableModel.TYPE,
157                 getContext().getReferentialService().getPhotoTypes(), false);
158         typeCol.setSortable(true);
159         typeCol.setMinWidth(100);
160 
161         // La colonne legende
162         final TableColumnExt captionCol = addColumn(PhotosTableModel.CAPTION);
163         captionCol.setSortable(true);
164         captionCol.setMinWidth(100);
165 
166         // La colonne date
167         final TableColumnExt dateCol = addDatePickerColumnToModel(PhotosTableModel.DATE, getConfig().getDateFormat());
168         dateCol.setSortable(true);
169         dateCol.setMinWidth(100);
170 
171         // La colonne prelevement
172         samplingOperationCellEditor = newExtendedComboBoxCellEditor(null, PhotosTableModel.SAMPLING_OPERATION, false);
173         final TableColumnExt samplingCol = addColumn(
174                 samplingOperationCellEditor,
175                 newTableCellRender(PhotosTableModel.SAMPLING_OPERATION),
176                 PhotosTableModel.SAMPLING_OPERATION);
177         samplingCol.setSortable(true);
178         samplingCol.setMinWidth(200);
179 
180         // La colonne direction
181         final TableColumnExt directionCol = addColumn(PhotosTableModel.DIRECTION);
182         directionCol.setSortable(true);
183         directionCol.setMinWidth(200);
184 
185         // La colonne chemin physique
186         final TableColumnExt pathCol = addColumn(
187                 null,
188                 (table, value, isSelected, hasFocus, row, column) -> {
189 
190                     if (!getTableModel().getEntry(getTable().convertRowIndexToModel(row)).isFileExists()) {
191                         value = t("dali.photo.unavailable");
192                     }
193 
194                     TableCellRenderer defaultRenderer = table.getDefaultRenderer(PhotosTableModel.PATH.getPropertyType());
195                     return defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
196                 },
197                 PhotosTableModel.PATH);
198         pathCol.setSortable(true);
199         pathCol.setMinWidth(200);
200         pathCol.setEditable(false);
201 
202         // Modele de la table
203         final PhotosTableModel tableModel = new PhotosTableModel(getTable().getColumnModel());
204         getTable().setModel(tableModel);
205 
206         // Colonne toujours visible
207         nameCol.setHideable(false);
208         samplingCol.setHideable(false);
209 
210         // Initialisation de la table
211         initTable(getTable());
212 
213         // Les colonne non visibles
214         directionCol.setVisible(false);
215         pathCol.setVisible(false);
216 
217         getTable().setVisibleRowCount(5);
218 
219         // border
220         addEditionPanelBorder();
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     public SwingValidator<PhotosTabUIModel> getValidator() {
228         return getUI().getValidator();
229     }
230 
231     /**
232      * Load observation.
233      */
234     private void load() {
235 
236         // update combo
237         samplingOperationCellEditor.getCombo().setData(new ArrayList<>(getModel().getObservationModel().getSamplingOperations()));
238 
239         // All photos
240         final List<PhotoDTO> photos = getModel().getObservationModel().getPhotos();
241 
242         // Init photos table
243         getTableModel().setReadOnly(!getModel().getObservationModel().isEditable());
244         getModel().setBeans(photos);
245         recomputeRowsValidState();
246 
247         if (getModel().getPhotoIndex() == null && getModel().getRowCount() > 0) {
248             getModel().setPhotoIndex(getModel().getFirstPhotoIndex());
249         }
250 
251     }
252 
253     /**
254      * <p>removePhoto.</p>
255      */
256     void removePhoto() {
257 
258         if (getModel().getSelectedRows().isEmpty()) {
259             LOG.warn("No selected photo");
260             return;
261         }
262 
263         // Confirm deleting
264         if (askBeforeDelete(t("dali.action.delete.photos.title"), t("dali.action.delete.photos.message"))) {
265             // Remove from model
266             getModel().deleteSelectedRows();
267             // refresh photo index
268             updatePhotoViewerContent(false);
269 
270             getModel().setModify(true);
271         }
272     }
273 
274     /**
275      * <p>firstPhoto.</p>
276      */
277     void firstPhoto() {
278         if (!getModel().getPhotoIndex().equals(getModel().getFirstPhotoIndex())) {
279             getModel().setPhotoIndex(getModel().getFirstPhotoIndex());
280         }
281     }
282 
283     /**
284      * <p>previousPhoto.</p>
285      */
286     void previousPhoto() {
287         if (!getModel().getPhotoIndex().equals(getModel().getFirstPhotoIndex())) {
288             getModel().setPhotoIndex(getModel().getPhotoIndex() - 1);
289         }
290     }
291 
292     /**
293      * <p>nextPhoto.</p>
294      */
295     void nextPhoto() {
296         if (!getModel().getPhotoIndex().equals(getModel().getLastPhotoIndex())) {
297             getModel().setPhotoIndex(getModel().getPhotoIndex() + 1);
298         }
299     }
300 
301     /**
302      * <p>lastPhoto.</p>
303      */
304     void lastPhoto() {
305         if (!getModel().getPhotoIndex().equals(getModel().getLastPhotoIndex())) {
306             getModel().setPhotoIndex(getModel().getLastPhotoIndex());
307         }
308     }
309 
310     public void fullScreen() {
311         if (getModel().getPhotoIndex() == null) return;
312         PhotosTableRowModel selectedPhoto = getModel().getSelectedPhoto();
313         if (selectedPhoto == null) return;
314 
315         PhotoViewer<PhotoDTO> photoViewer = new PhotoViewer<>();
316 
317         photoViewer.setPhoto(selectedPhoto, Images.ImageType.BASE);
318         openFrame(photoViewer, selectedPhoto.getName(), null, getUI().getBounds());
319     }
320 
321     /**
322      * Initialisation des listeners.
323      */
324     private void initListeners() {
325 
326         getModel().addPropertyChangeListener(PhotosTabUIModel.PROPERTY_OBSERVATION_MODEL, evt -> load());
327 
328         getModel().addPropertyChangeListener(PhotosTabUIModel.PROPERTY_PHOTO_INDEX, evt -> {
329             if (getModel().isModelAdjusting())
330                 return;
331 
332             PhotosTableRowModel selectedPhoto = getModel().getSelectedPhoto();
333             if (selectedPhoto != null) {
334                 setFocusOnCell(selectedPhoto);
335             }
336         });
337 
338         // listener on select photo in viewer
339         getUI().getPhotoViewer().addPropertyChangeListener(PhotoViewer.EVENT_PHOTO_CLICKED, evt -> {
340 
341             if (evt.getNewValue() instanceof PhotoDTO) {
342 
343                 if (getModel().isModelAdjusting()) {
344                     return;
345                 }
346                 try {
347 
348                     getModel().setModelAdjusting(true);
349 
350                     PhotoDTO photo = (PhotoDTO) evt.getNewValue();
351 
352                     PhotosTableRowModel rowModel = null;
353                     if (photo instanceof PhotosTableRowModel) {
354                         rowModel = (PhotosTableRowModel) photo;
355                     } else {
356                         for (PhotosTableRowModel row : getModel().getRows()) {
357                             if (Objects.equals(row.getFullPath(), photo.getFullPath())) {
358                                 rowModel = row;
359                                 break;
360                             }
361                         }
362                     }
363 
364                     if (rowModel != null) {
365 
366                         if (rowModel.isFileDownloadable()) {
367 
368                             // Download the photo
369                             DownloadAction downloadAction = getContext().getActionFactory().createLogicAction(this, DownloadAction.class);
370                             downloadAction.setToDownload(rowModel);
371                             getContext().getActionEngine().runAction(downloadAction);
372 
373                         } else if (rowModel != getModel().getSingleSelectedRow()) {
374 
375                             // select the photo in table
376                             setFocusOnCell(rowModel);
377                             getModel().setPhotoIndex(getTableModel().getRowIndex(rowModel));
378                         }
379                     }
380 
381                 } finally {
382 
383                     getModel().setModelAdjusting(false);
384                 }
385             }
386         });
387 
388         // listener on selected photo in table
389         getModel().addPropertyChangeListener(AbstractDaliTableUIModel.PROPERTY_SELECTED_ROWS, evt -> {
390 
391             if (getModel().isModelAdjusting())
392                 return;
393 
394             getModel().setModelAdjusting(true);
395             getModel().setPhotoIndex(getTableModel().getRowIndex(getModel().getSingleSelectedRow()));
396             updatePhotoViewerContent(true);
397             getModel().setModelAdjusting(false);
398 
399         });
400 
401         // Listener on combo
402         getUI().getTypeDiaporamaComboBox().addActionListener(e -> updatePhotoViewerContent(false));
403 
404     }
405 
406     /**
407      * {@inheritDoc}
408      */
409     @Override
410     public boolean onHideTab(int currentIndex, int newIndex) {
411         return true;
412     }
413 
414     /**
415      * {@inheritDoc}
416      */
417     @Override
418     public void onShowTab(int currentIndex, int newIndex) {
419 
420     }
421 
422     /**
423      * {@inheritDoc}
424      */
425     @Override
426     public boolean onRemoveTab() {
427         return false;
428     }
429 
430     /**
431      * <p>saveActualModel.</p>
432      */
433     public void saveActualModel() {
434         getModel().setModelAdjusting(true);
435         getModel().getObservationModel().setPhotos(getModel().getBeans());
436         getModel().setModelAdjusting(false);
437     }
438 
439     /**
440      * {@inheritDoc}
441      */
442     @Override
443     public PhotosTableModel getTableModel() {
444         return (PhotosTableModel) getTable().getModel();
445     }
446 
447     /**
448      * {@inheritDoc}
449      */
450     @Override
451     public SwingTable getTable() {
452         return getUI().getPhotoTable();
453     }
454 
455     /**
456      * Mettre a jour la ou les photos dans l'écran.
457      *
458      * @param selectionOnly
459      */
460     public void updatePhotoViewerContent(boolean selectionOnly) {
461 
462         // update photo index if model has changed
463         Integer photoIndex = getModel().getPhotoIndex();
464         if (photoIndex != null) {
465             photoIndex = Math.min(photoIndex, getModel().getLastPhotoIndex());
466             photoIndex = photoIndex == -1 ? null : photoIndex;
467             if (!Objects.equals(getModel().getPhotoIndex(), photoIndex)) {
468                 getModel().setPhotoIndex(photoIndex);
469                 return;
470             }
471         }
472 
473         PhotoLoader worker = new PhotoLoader(selectionOnly);
474         getModel().setLoading(true);
475         worker.execute();
476 
477     }
478 
479     private class PhotoLoader extends SwingWorker<Object, Object> {
480 
481         private final boolean selectionOnly;
482 
483         private PhotoLoader(boolean selectionOnly) {
484             this.selectionOnly = selectionOnly;
485         }
486 
487         @Override
488         protected Object doInBackground() {
489 
490             PhotoViewType viewType = getUI().getTypeDiaporamaComboBox().getSelectedItem();
491 
492             if (PhotoViewType.VIEW_DIAPO.equals(viewType)) {
493 
494                 getUI().getPhotoViewer().setPhoto(getModel().getSingleSelectedRow(), viewType.getImageType());
495 
496             } else if (PhotoViewType.VIEW_THUMBNAIL.equals(viewType)) {
497 
498                 if (!selectionOnly) {
499                     // load all photos from model
500                     getUI().getPhotoViewer().setPhotos(getModel().getRows(), viewType.getImageType());
501                 }
502                 getUI().getPhotoViewer().setSelected(getModel().getSelectedRows());
503 
504             }
505 
506             return null;
507         }
508 
509         @Override
510         protected void done() {
511             try {
512                 get();
513             } catch (InterruptedException | ExecutionException e) {
514                 throw new DaliTechnicalException(e.getMessage(), e);
515             }
516 
517             // affiche l indice photos
518             getUI().getPhotoIndexLabel().setText(String.format("%s / %s", getModel().getPhotoIndex() == null ? 0 : getModel().getPhotoIndex() + 1, getModel().getRowCount()));
519 
520             // Gestion des boutons de parcours des photos
521             getUI().getFirstPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getFirstPhotoIndex());
522             getUI().getPreviousPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getFirstPhotoIndex());
523             getUI().getNextPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getLastPhotoIndex());
524             getUI().getLastPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getLastPhotoIndex());
525 
526             getModel().setLoading(false);
527 
528         }
529     }
530 }