Commit cd46b0af authored by PECQUOT's avatar PECQUOT
Browse files

[enh] Refactor photos UI (Mantis #48755)


Signed-off-by: PECQUOT's avatarlp1ee9d <ludovic.pecquot@e-is.pro>
parent b6f1b554
......@@ -23,10 +23,18 @@ package fr.ifremer.reefdb.ui.swing.content.observation.photo;
* #L%
*/
import com.google.common.collect.ImmutableList;
import fr.ifremer.quadrige3.core.service.http.HttpNotFoundException;
import fr.ifremer.reefdb.service.ReefDbBusinessException;
import fr.ifremer.reefdb.service.ReefDbServiceLocator;
import fr.ifremer.reefdb.ui.swing.action.AbstractReefDbAction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.nuiton.i18n.I18n.t;
......@@ -35,7 +43,9 @@ import static org.nuiton.i18n.I18n.t;
*/
public class DownloadAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosTabUI, PhotosTabUIHandler> {
private boolean downloaded;
private static final Log LOG = LogFactory.getLog(DownloadAction.class);
private boolean allDownloaded;
private Collection<PhotosTableRowModel> toDownload;
/**
* Constructor.
......@@ -44,6 +54,19 @@ public class DownloadAction extends AbstractReefDbAction<PhotosTabUIModel, Photo
*/
public DownloadAction(PhotosTabUIHandler handler) {
super(handler, false);
setActionDescription(t("reefdb.photo.download.tip"));
}
public Collection<PhotosTableRowModel> getToDownload() {
return Optional.ofNullable(toDownload).orElse(getModel().getSelectedRows());
}
public void setToDownload(PhotosTableRowModel toDownload) {
this.toDownload = ImmutableList.of(toDownload);
}
public void setToDownload(Collection<PhotosTableRowModel> toDownload) {
this.toDownload = toDownload;
}
/**
......@@ -51,13 +74,7 @@ public class DownloadAction extends AbstractReefDbAction<PhotosTabUIModel, Photo
*/
@Override
public boolean prepareAction() throws Exception {
if (!super.prepareAction() || getModel().getSelectedRows().isEmpty()) {
return false;
}
createProgressionUIModel();
downloaded = false;
return getModel().getSingleSelectedRow().isFileDownloadable();
return super.prepareAction() && !getToDownload().isEmpty() && getToDownload().stream().anyMatch(PhotosTableRowModel::isFileDownloadable);
}
/**
......@@ -66,17 +83,32 @@ public class DownloadAction extends AbstractReefDbAction<PhotosTabUIModel, Photo
@Override
public void doAction() throws Exception {
try {
downloaded = ReefDbServiceLocator.instance().getSynchroRestClientService().downloadPhoto(
getContext().getAuthenticationInfo(),
getModel().getSingleSelectedRow().getRemoteId(),
getModel().getSingleSelectedRow().getFullPath(),
getProgressionUIModel()
);
} catch (HttpNotFoundException e) {
throw new ReefDbBusinessException(t("reefdb.photo.download.notFound", getModel().getSingleSelectedRow().getName()));
}
allDownloaded = true;
createProgressionUIModel();
List<PhotosTableRowModel> downloadablePhotos = getToDownload().stream().filter(PhotosTableRowModel::isFileDownloadable).collect(Collectors.toList());
int nbPhotos = downloadablePhotos.size();
int nPhoto = 0;
for (PhotosTableRowModel photo: downloadablePhotos) {
try {
getProgressionUIModel().setMessage(String.format("%s / %s", ++nPhoto, nbPhotos));
boolean downloaded = ReefDbServiceLocator.instance().getSynchroRestClientService().downloadPhoto(
getContext().getAuthenticationInfo(),
photo.getRemoteId(),
photo.getFullPath(),
getProgressionUIModel()
);
if (!downloaded) {
LOG.warn(String.format("the photo %s was not download, but without exception", photo.getName()));
}
allDownloaded &= downloaded;
} catch (HttpNotFoundException e) {
throw new ReefDbBusinessException(t("reefdb.photo.download.notFound", photo.getName()));
}
}
}
/**
......@@ -85,20 +117,15 @@ public class DownloadAction extends AbstractReefDbAction<PhotosTabUIModel, Photo
@Override
public void postSuccessAction() {
if (!downloaded)
if (!allDownloaded)
getContext().getDialogHelper().showErrorDialog(t("reefdb.photo.download.error"));
getHandler().updatePhotoViewerContent();
getHandler().updatePhotoViewerContent(false);
getUI().invalidate();
getUI().repaint();
getUI().processDataBinding(PhotosTabUI.BINDING_DOWNLOAD_PHOTO_BUTTON_ENABLED);
getHandler().updateControls();
super.postSuccessAction();
}
@Override
protected void releaseAction() {
downloaded = false;
super.releaseAction();
}
}
......@@ -24,15 +24,18 @@ package fr.ifremer.reefdb.ui.swing.content.observation.photo;
*/
import fr.ifremer.quadrige3.core.dao.technical.Files;
import fr.ifremer.reefdb.dto.ReefDbBeans;
import fr.ifremer.reefdb.dto.data.photo.PhotoDTO;
import fr.ifremer.quadrige3.ui.swing.ApplicationUIUtil;
import fr.ifremer.reefdb.service.ReefDbBusinessException;
import fr.ifremer.reefdb.ui.swing.action.AbstractReefDbAction;
import javax.swing.JEditorPane;
import javax.swing.JOptionPane;
import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import static org.nuiton.i18n.I18n.t;
......@@ -70,10 +73,8 @@ public class ExportAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosT
@Override
public void doAction() throws Exception {
List<String> filePaths = ReefDbBeans.collectProperties(getModel().getSelectedBeans(), PhotoDTO.PROPERTY_FULL_PATH);
for (String filePath: filePaths) {
Path fileSrc = Paths.get(filePath);
for (PhotosTableRowModel photo : getModel().getSelectedRows().stream().filter(PhotosTableRowModel::isFileExists).collect(Collectors.toList())) {
Path fileSrc = Paths.get(photo.getFullPath());
if (!java.nio.file.Files.isRegularFile(fileSrc)) {
throw new ReefDbBusinessException(t("quadrige3.error.file.not.exists"));
}
......@@ -87,7 +88,27 @@ public class ExportAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosT
@Override
public void postSuccessAction() {
displayInfoMessage(t("reefdb.action.photo.export.title"), t("reefdb.action.photo.export.done", destDir.getAbsolutePath()));
String text = t("reefdb.action.photo.export.done",
destDir.getAbsoluteFile().toURI(),
destDir.getAbsolutePath()
);
JEditorPane editorPane = new JEditorPane("text/html", text);
editorPane.setEditable(false);
editorPane.addHyperlinkListener(e -> {
if (HyperlinkEvent.EventType.ACTIVATED == e.getEventType()) {
URL url = e.getURL();
ApplicationUIUtil.openLink(url);
}
});
getContext().getDialogHelper().showOptionDialog(
getUI(),
editorPane,
t("reefdb.action.photo.export.title"),
JOptionPane.INFORMATION_MESSAGE,
JOptionPane.DEFAULT_OPTION
);
super.postSuccessAction();
}
......
......@@ -20,83 +20,78 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
#L%
-->
<Table id="photoTabUI" fill="both" decorator='help' implements='fr.ifremer.reefdb.ui.swing.util.ReefDbUI&lt;PhotosTabUIModel, PhotosTabUIHandler&gt;'>
<import>
fr.ifremer.reefdb.ui.swing.ReefDbHelpBroker
fr.ifremer.reefdb.ui.swing.ReefDbUIContext
fr.ifremer.reefdb.ui.swing.util.ReefDbUI
fr.ifremer.quadrige3.ui.swing.ApplicationUI
fr.ifremer.quadrige3.ui.swing.ApplicationUIUtil
java.awt.FlowLayout
javax.swing.Box
javax.swing.BoxLayout
java.awt.BorderLayout
<JPanel id="photoTabUI" decorator='help' layout="{new BorderLayout()}"
implements='fr.ifremer.reefdb.ui.swing.util.ReefDbUI&lt;PhotosTabUIModel, PhotosTabUIHandler&gt;'>
<import>
fr.ifremer.reefdb.ui.swing.ReefDbHelpBroker
fr.ifremer.reefdb.ui.swing.ReefDbUIContext
fr.ifremer.reefdb.ui.swing.util.ReefDbUI
fr.ifremer.quadrige3.ui.swing.ApplicationUI
fr.ifremer.quadrige3.ui.swing.ApplicationUIUtil
java.awt.FlowLayout
javax.swing.Box
javax.swing.BoxLayout
java.awt.BorderLayout
org.jdesktop.swingx.JXImageView
org.jdesktop.swingx.JXList
org.jdesktop.swingx.JXImageView
org.jdesktop.swingx.JXList
fr.ifremer.reefdb.ui.swing.util.image.PhotoViewer
fr.ifremer.quadrige3.ui.swing.plaf.WaitBlockingLayerUI
fr.ifremer.quadrige3.ui.swing.table.SwingTable
fr.ifremer.quadrige3.ui.swing.component.EnumValueComboBox
fr.ifremer.reefdb.ui.swing.util.image.PhotoViewer
fr.ifremer.quadrige3.ui.swing.plaf.WaitBlockingLayerUI
fr.ifremer.quadrige3.ui.swing.table.SwingTable
fr.ifremer.quadrige3.ui.swing.component.EnumValueComboBox
static org.nuiton.i18n.I18n.*
</import>
static org.nuiton.i18n.I18n.*
</import>
<!--getContextValue est une méthode interne JAXX-->
<PhotosTabUIModel id='model' initializer='getContextValue(PhotosTabUIModel.class)'/>
<!--getContextValue est une méthode interne JAXX-->
<PhotosTabUIModel id='model' initializer='getContextValue(PhotosTabUIModel.class)'/>
<ReefDbHelpBroker id='broker' constructorParams='"reefdb.home.help"'/>
<ReefDbHelpBroker id='broker' constructorParams='"reefdb.home.help"'/>
<BeanValidator id='validator' bean='model' uiClass='jaxx.runtime.validator.swing.ui.ImageValidationUI'>
<field name='instance' component='photoTablePanel'/>
</BeanValidator>
<BeanValidator id='validator' bean='model' uiClass='jaxx.runtime.validator.swing.ui.ImageValidationUI'>
<field name='instance' component='photoTablePanel'/>
</BeanValidator>
<WaitBlockingLayerUI id='photoBlockLayer'/>
<WaitBlockingLayerUI id='photoBlockLayer'/>
<script><![CDATA[
<script><![CDATA[
//Le parent est très utile pour intervenir sur les frères
public PhotosTabUI(ApplicationUI parentUI) {
ApplicationUIUtil.setParentUI(this, parentUI);
}
]]></script>
<row>
<cell weightx='1' weighty='0.7' fill='both'>
<JPanel layout="{new BorderLayout()}">
<JPanel layout="{new BorderLayout()}" constraints="BorderLayout.CENTER">
<PhotoViewer id="photoViewer" genericType="PhotosTableRowModel" decorator="boxed" constraints="BorderLayout.CENTER"/>
<PhotoViewer id="photoViewer" genericType="PhotosTableRowModel" decorator="boxed" constraints="BorderLayout.CENTER"/>
<JPanel constraints="BorderLayout.PAGE_END">
<JButton id="firstPhotoButton" onActionPerformed="handler.firstPhoto()"/>
<JButton id="previousPhotoButton" onActionPerformed="handler.previousPhoto()"/>
<JLabel id="photoIndexLabel"/>
<JButton id="nextPhotoButton" onActionPerformed="handler.nextPhoto()"/>
<JButton id="lastPhotoButton" onActionPerformed="handler.lastPhoto()"/>
<JLabel id="typeDiaporamaLabel"/>
<EnumValueComboBox id="typeDiaporamaComboBox" genericType='PhotoViewType' constructorParams='PhotoViewType.class'/>
<JButton id="fullScreenButton" onActionPerformed="handler.fullScreen()"/>
</JPanel>
</JPanel>
</cell>
</row>
<row>
<cell weightx='1' weighty='0.3' fill='both'>
<JPanel id="photoTablePanel" layout="{new BorderLayout()}">
<JScrollPane constraints="BorderLayout.CENTER">
<SwingTable id='photoTable'/>
</JScrollPane>
<JPanel layout="{new BorderLayout()}" constraints="BorderLayout.PAGE_END">
<JPanel constraints="BorderLayout.LINE_START">
<JButton id='importPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
<JButton id='downloadPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
<JButton id='deletePhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}' onActionPerformed="handler.removePhoto()"/>
</JPanel>
<JPanel constraints='BorderLayout.LINE_END'>
<JButton id='exportPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
</JPanel>
</JPanel>
</JPanel>
</cell>
</row>
</Table>
\ No newline at end of file
<JPanel constraints="BorderLayout.PAGE_END">
<JButton id="firstPhotoButton" onActionPerformed="handler.firstPhoto()"/>
<JButton id="previousPhotoButton" onActionPerformed="handler.previousPhoto()"/>
<JLabel id="photoIndexLabel"/>
<JButton id="nextPhotoButton" onActionPerformed="handler.nextPhoto()"/>
<JButton id="lastPhotoButton" onActionPerformed="handler.lastPhoto()"/>
<JLabel id="typeDiaporamaLabel"/>
<EnumValueComboBox id="typeDiaporamaComboBox" genericType='PhotoViewType' constructorParams='PhotoViewType.class'/>
<JButton id="fullScreenButton" onActionPerformed="handler.fullScreen()"/>
</JPanel>
</JPanel>
<JPanel id="photoTablePanel" layout="{new BorderLayout()}" constraints="BorderLayout.PAGE_END">
<JScrollPane constraints="BorderLayout.CENTER">
<SwingTable id='photoTable'/>
</JScrollPane>
<JPanel layout="{new BorderLayout()}" constraints="BorderLayout.PAGE_END">
<JPanel constraints="BorderLayout.LINE_START">
<JButton id='importPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
<JButton id='downloadPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
<JButton id='deletePhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}' onActionPerformed="handler.removePhoto()"/>
</JPanel>
<JPanel constraints='BorderLayout.LINE_END'>
<JButton id='exportPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
</JPanel>
</JPanel>
</JPanel>
</JPanel>
\ No newline at end of file
......@@ -43,7 +43,7 @@
text: "reefdb.photo.download";
toolTipText: "reefdb.photo.download.tip";
_applicationAction: {DownloadAction.class};
enabled: {model.getSingleSelectedRow().isFileDownloadable()};
enabled: {model.isDownloadEnabled()};
}
#deletePhotoButton {
......@@ -58,7 +58,7 @@
text: "reefdb.common.export";
toolTipText: "reefdb.photo.export.tip";
_applicationAction: {ExportAction.class};
enabled: {!model.getSelectedRows().isEmpty()};
enabled: {model.isExportEnabled()};
}
#firstPhotoButton {
......
......@@ -78,12 +78,6 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
// Init viewer
SwingUtil.setLayerUI(getUI().getPhotoViewer(), getUI().getPhotoBlockLayer());
// Intialisation de la combo affichage
// final List<String> valeurComboBox = new ArrayList<>();
// valeurComboBox.add(PhotosTabUIModel.TYPE_AFFICHAGE_DIAPORAMA);
// valeurComboBox.add(PhotosTabUIModel.TYPE_AFFICHAGE_MINIATURE);
// SwingUtil.fillComboBox(getUI().getTypeDiaporamaComboBox(), valeurComboBox, PhotosTabUIModel.TYPE_AFFICHAGE_DIAPORAMA);
// init table
initTable();
......@@ -93,6 +87,10 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
// Register validator
registerValidators(getValidator());
listenValidatorValid(getValidator(), getModel());
// initial state
ui.getDownloadPhotoButton().setEnabled(false);
ui.getExportPhotoButton().setEnabled(false);
}
/** {@inheritDoc} */
......@@ -253,7 +251,7 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
// Remove from model
getModel().deleteSelectedRows();
// refresh photo index
updatePhotoViewerContent();
updatePhotoViewerContent(false);
getModel().setModify(true);
}
......@@ -314,11 +312,13 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
getModel().addPropertyChangeListener(PhotosTabUIModel.PROPERTY_OBSERVATION_MODEL, evt -> load());
getModel().addPropertyChangeListener(PhotosTabUIModel.PROPERTY_PHOTO_INDEX, evt -> {
if (getModel().isModelAdjusting())
return;
PhotosTableRowModel selectedPhoto = getModel().getSelectedPhoto();
if (selectedPhoto != null) {
setFocusOnCell(selectedPhoto);
}
updatePhotoViewerSelection();
});
// listener on select photo in viewer
......@@ -329,52 +329,79 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
if (getModel().isModelAdjusting()) {
return;
}
getModel().setModelAdjusting(true);
PhotoDTO photo = (PhotoDTO) evt.getNewValue();
PhotosTableRowModel rowModel = null;
if (photo instanceof PhotosTableRowModel) {
rowModel = (PhotosTableRowModel) photo;
} else {
for (PhotosTableRowModel row : getModel().getRows()) {
if (Objects.equals(row.getFullPath(), photo.getFullPath())) {
rowModel = row;
break;
try {
getModel().setModelAdjusting(true);
PhotoDTO photo = (PhotoDTO) evt.getNewValue();
PhotosTableRowModel rowModel = null;
if (photo instanceof PhotosTableRowModel) {
rowModel = (PhotosTableRowModel) photo;
} else {
for (PhotosTableRowModel row : getModel().getRows()) {
if (Objects.equals(row.getFullPath(), photo.getFullPath())) {
rowModel = row;
break;
}
}
}
}
if (rowModel != null) {
setFocusOnCell(rowModel);
getModel().setPhotoIndex(getTableModel().getRowIndex(rowModel));
}
if (rowModel != null) {
if (rowModel.isFileDownloadable()) {
// Download the photo
DownloadAction downloadAction = getContext().getActionFactory().createLogicAction(this, DownloadAction.class);
downloadAction.setToDownload(rowModel);
getContext().getActionEngine().runAction(downloadAction);
} else if (rowModel != getModel().getSingleSelectedRow()) {
getModel().setModelAdjusting(false);
// select the photo in table
setFocusOnCell(rowModel);
getModel().setPhotoIndex(getTableModel().getRowIndex(rowModel));
}
}
} finally {
getModel().setModelAdjusting(false);
}
}
});
// listener on selected photo in table
getModel().addPropertyChangeListener(AbstractReefDbTableUIModel.PROPERTY_SINGLE_ROW_SELECTED, evt -> {
updateControls();
if (getModel().getSingleSelectedRow() == null || getModel().isModelAdjusting()) {
return;
}
getModel().setModelAdjusting(true);
getModel().setPhotoIndex(getTableModel().getRowIndex(getModel().getSingleSelectedRow()));
updatePhotoViewerContent(true);
getModel().setModelAdjusting(false);
});
// Listener on table selection
getModel().addPropertyChangeListener(AbstractReefDbTableUIModel.PROPERTY_SELECTED_ROWS, evt -> getUI().processDataBinding(PhotosTabUI.BINDING_EXPORT_PHOTO_BUTTON_ENABLED));
});
// Listener on combo
getUI().getTypeDiaporamaComboBox().addActionListener(e -> updatePhotoViewerContent());
getUI().getTypeDiaporamaComboBox().addActionListener(e -> updatePhotoViewerContent(false));
}
/** {@inheritDoc} */
public void updateControls() {
// update buttons states
getUI().processDataBinding(PhotosTabUI.BINDING_EXPORT_PHOTO_BUTTON_ENABLED);
getUI().processDataBinding(PhotosTabUI.BINDING_DOWNLOAD_PHOTO_BUTTON_ENABLED);
}
/**
* {@inheritDoc}
*/
@Override
public boolean onHideTab(int currentIndex, int newIndex) {
return true;
......@@ -416,7 +443,7 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
/**
* Mettre a jour la ou les photos dans l'écran.
*/
public void updatePhotoViewerContent() {
public void updatePhotoViewerContent(boolean selectionOnly) {
// update photo index if model has changed
Integer photoIndex = getModel().getPhotoIndex();
......@@ -429,47 +456,18 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
}
}
PhotoLoader worker = new PhotoLoader(getModel().getSelectedPhoto());
PhotoLoader worker = new PhotoLoader(selectionOnly);
getModel().setLoading(true);
worker.execute();
}
private void updatePhotoViewerSelection() {
PhotoViewType viewType = getUI().getTypeDiaporamaComboBox().getSelectedItem();
if (PhotoViewType.VIEW_DIAPO.equals(viewType)) {
getUI().getPhotoViewer().setPhoto(getModel().getSelectedPhoto(), viewType.getImageType());
} else if (PhotoViewType.VIEW_THUMBNAIL.equals(viewType)) {
getUI().getPhotoViewer().setSelected(getModel().getSelectedPhoto());
}
updateControls();
}
private void updateControls() {
// affiche l indice photos
getUI().getPhotoIndexLabel().setText(String.format("%s / %s", getModel().getPhotoIndex() == null ? 0 : getModel().getPhotoIndex() + 1, getModel().getRowCount()));
// Gestion des boutons de parcours des photos
getUI().getFirstPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getFirstPhotoIndex());
getUI().getPreviousPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getFirstPhotoIndex());
getUI().getNextPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getLastPhotoIndex());
getUI().getLastPhotoButton().setEnabled(getModel().getPhotoIndex() != null && getModel().getPhotoIndex() != getModel().getLastPhotoIndex());
}
private class PhotoLoader extends SwingWorker<Object, Object> {
private final PhotosTableRowModel selectedPhoto;
private final boolean selectionOnly;
private PhotoLoader(PhotosTableRowModel selectedPhoto) {
this.selectedPhoto = selectedPhoto;
private PhotoLoader(boolean selectionOnly) {
this.selectionOnly = selectionOnly;
}
@Override
......@@ -479,12 +477,15 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
if (PhotoViewType.VIEW_DIAPO.equals(viewType)) {
getUI().getPhotoViewer().setPhoto(selectedPhoto, viewType.getImageType());
getUI().getPhotoViewer().setPhoto(getModel().getSingleSelectedRow(), viewType.getImageType());
} else if (PhotoViewType.VIEW_THUMBNAIL.equals(viewType)) {