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