1 package fr.ifremer.reefdb.ui.swing.util.image;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.collect.ImmutableList;
27 import fr.ifremer.quadrige3.core.dao.technical.Assert;
28 import fr.ifremer.quadrige3.core.dao.technical.Images;
29 import fr.ifremer.reefdb.dto.data.photo.PhotoDTO;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33
34 import javax.swing.*;
35 import javax.swing.border.EmptyBorder;
36 import javax.swing.event.ChangeEvent;
37 import javax.swing.event.ChangeListener;
38 import java.awt.*;
39 import java.awt.event.MouseAdapter;
40 import java.awt.event.MouseEvent;
41 import java.awt.event.MouseWheelEvent;
42 import java.awt.image.BufferedImage;
43 import java.io.File;
44 import java.util.List;
45 import java.util.*;
46
47 import static org.nuiton.i18n.I18n.t;
48
49
50
51
52
53
54 public class PhotoViewer<P extends PhotoDTO> extends JPanel implements ChangeListener {
55
56 private final static Log log = LogFactory.getLog(PhotoViewer.class);
57
58
59
60
61 public static final String EVENT_PHOTO_CLICKED = "photoClicked";
62
63
64
65 private static final int PROPERTY_DEFAULT_THUMBNAILS_COLUMNS = 6;
66
67 private final JPanel imageGrid;
68 private final GridLayout imageGridLayout;
69 private final JScrollPane imageScroll;
70 private final Map<P, BackgroundPanel> photoMap;
71 private Images.ImageType type;
72 private JSlider zoomSlider;
73 private double ratio = BackgroundPanel.NO_SCALE;
74
75
76
77
78 public PhotoViewer() {
79 setLayout(new BorderLayout());
80 setBorder(new EmptyBorder(5, 5, 5, 5));
81 photoMap = new LinkedHashMap<>();
82 imageGridLayout = new GridLayout();
83 imageGrid = new JPanel(imageGridLayout);
84 imageScroll = new JScrollPane(imageGrid);
85 add(imageScroll, BorderLayout.CENTER);
86 }
87
88 public void setType(Images.ImageType type) {
89 this.type = type;
90
91 }
92
93 public void setPhoto(P photo, Images.ImageType type) {
94 setPhotos(photo != null ? ImmutableList.of(photo) : null, type);
95 }
96
97
98
99
100
101
102 public void setPhotos(Collection<P> photos, Images.ImageType type) {
103
104 Assert.notNull(type);
105 setType(type);
106 photoMap.clear();
107
108 try {
109 if (photos == null) {
110 return;
111 }
112
113 for (P photo : photos) {
114
115 if (photo == null) {
116 continue;
117 }
118
119 if (StringUtils.isBlank(photo.getFullPath())) {
120 log.warn("invalid photo object (no path)");
121 continue;
122 }
123
124 BufferedImage image = loadPhoto(photo, type);
125 BackgroundPanel imagePanel = new BackgroundPanel(image, true);
126 photoMap.put(photo, imagePanel);
127 }
128
129 } finally {
130 updateImageGrid();
131 }
132 }
133
134
135
136
137
138
139 public void setSelected(P photo) {
140 setSelected(ImmutableList.of(photo));
141 }
142
143
144
145
146
147
148 public void setSelected(Collection<P> photos) {
149
150
151 photoMap.values().forEach(backgroundPanel -> backgroundPanel.setSelected(false));
152
153 if (photos == null) return;
154
155 photos.stream().map(photoMap::get).filter(Objects::nonNull).forEach(backgroundPanel -> {
156 backgroundPanel.setSelected(true);
157 if (!SwingUtilities.isRectangleContainingRectangle(imageScroll.getViewport().getViewRect(), backgroundPanel.getBounds()))
158 backgroundPanel.scrollRectToVisible(imageScroll.getViewport().getVisibleRect());
159 backgroundPanel.revalidate();
160 });
161 }
162
163 private BufferedImage loadPhoto(P photo, Images.ImageType type) {
164 BufferedImage image = Images.loadImage(new File(photo.getFullPath()), type, true);
165 if (image == null && type.getMaxSize() != null) {
166
167 image = new BufferedImage(type.getMaxSize(), type.getMaxSize(), BufferedImage.TYPE_INT_ARGB);
168 Graphics2D graphics = image.createGraphics();
169
170 graphics.setColor(new Color(240, 240, 240));
171 graphics.fillRect(0, 0, type.getMaxSize(), type.getMaxSize());
172
173 graphics.setColor(Color.RED.brighter());
174 FontMetrics fm = graphics.getFontMetrics();
175 List<String> texts = new ArrayList<>();
176 texts.addAll(adjustText(fm, t("reefdb.photo.unavailable"), type.getMaxSize()));
177 texts.addAll(adjustText(fm, t("reefdb.photo.unavailable.tip"), type.getMaxSize()));
178 float textHeight = fm.getHeight();
179 float textTotalHeight = textHeight * texts.size();
180
181 for (int i = 0; i < texts.size(); i++) {
182 String text = texts.get(i);
183 graphics.drawString(text,
184 (float) type.getMaxSize() / 2 - ((float) fm.stringWidth(text)) / 2,
185 (float) type.getMaxSize() / 2 - textTotalHeight / 2 + fm.getAscent() + textHeight * i
186 );
187 }
188 }
189 return image;
190 }
191
192 private List<String> adjustText(FontMetrics fontMetrics, String text, int maxWidth) {
193 if (text == null)
194 text = "";
195 List<String> result = new ArrayList<>();
196
197
198 String[] words = text.split("\\s+");
199 if (words.length == 1) {
200
201 return ImmutableList.of(text);
202 }
203
204 StringBuilder line = new StringBuilder(words[0]);
205 int i = 0;
206 while (i < words.length - 1) {
207
208 if (fontMetrics.stringWidth(line + " " + words[i + 1]) < maxWidth) {
209 if (line.length() > 0) {
210
211 line.append(" ");
212 }
213
214 line.append(words[++i]);
215 } else {
216
217 result.add(line.toString());
218 line = new StringBuilder();
219 if (fontMetrics.stringWidth(words[i + 1]) > maxWidth) {
220
221 result.add(words[++i]);
222 }
223 }
224 }
225 if (line.length() > 0) {
226
227 result.add(line.toString());
228 }
229 return result;
230 }
231
232 private void updateImageGrid() {
233
234 imageGrid.removeAll();
235
236 if (type == Images.ImageType.THUMBNAIL) {
237
238 imageGridLayout.setColumns(PROPERTY_DEFAULT_THUMBNAILS_COLUMNS);
239 imageGridLayout.setRows(0);
240
241 } else {
242
243 imageGridLayout.setColumns(1);
244 imageGridLayout.setRows(1);
245
246 }
247
248 photoMap.values().forEach(backgroundPanel -> {
249 initBackgroundPanel(backgroundPanel);
250 imageGrid.add(backgroundPanel);
251 });
252
253 revalidate();
254 }
255
256 private void initBackgroundPanel(BackgroundPanel image) {
257
258
259 image.addMouseListener(mouseAdapter);
260 image.addMouseMotionListener(mouseAdapter);
261 image.addMouseWheelListener(mouseAdapter);
262 image.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
263
264 if (type == Images.ImageType.BASE) {
265 createZoomSlider();
266 } else if (zoomSlider != null) {
267 remove(zoomSlider);
268 }
269 }
270
271 private final MouseAdapter mouseAdapter = new MouseAdapter() {
272
273 private Point origin;
274
275 @Override
276 public void mouseClicked(MouseEvent e) {
277
278 if (e.getComponent() instanceof BackgroundPanel) {
279 for (P photo : photoMap.keySet()) {
280 if (e.getComponent() == photoMap.get(photo)) {
281 firePropertyChange(EVENT_PHOTO_CLICKED, null, photo);
282 return;
283 }
284 }
285 }
286
287 }
288
289 @Override
290 public void mousePressed(MouseEvent e) {
291 origin = new Point(e.getPoint());
292 e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
293 }
294
295 @Override
296 public void mouseReleased(MouseEvent e) {
297 e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
298 }
299
300 @Override
301 public void mouseWheelMoved(MouseWheelEvent e) {
302 if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL)
303 zoom(e.getWheelRotation());
304 }
305
306 @Override
307 public void mouseDragged(MouseEvent e) {
308 if (origin != null) {
309 JComponent component = (JComponent) e.getSource();
310 int deltaX = origin.x - e.getX();
311 int deltaY = origin.y - e.getY();
312
313 Rectangle view = imageScroll.getViewport().getViewRect();
314 view.x += deltaX;
315 view.y += deltaY;
316
317 component.scrollRectToVisible(view);
318 }
319 }
320
321 };
322
323
324
325
326 @Override
327 public void stateChanged(ChangeEvent e) {
328 if (e.getSource() != zoomSlider) return;
329 if (zoomSlider.getValueIsAdjusting()) return;
330 int zoom = ((JSlider) e.getSource()).getValue();
331 ratio = zoom / 100.0;
332
333 Arrays.stream(imageGrid.getComponents()).filter(component -> component instanceof BackgroundPanel)
334 .map(component -> (BackgroundPanel) component)
335 .forEach(backgroundPanel -> backgroundPanel.setRatio(ratio));
336
337 revalidate();
338 }
339
340 private void createZoomSlider() {
341 zoomSlider = new JSlider(JSlider.HORIZONTAL, 10, 100, 100);
342 zoomSlider.setMajorTickSpacing(10);
343 zoomSlider.setPaintTicks(true);
344 zoomSlider.setPaintLabels(true);
345 zoomSlider.setSnapToTicks(true);
346 zoomSlider.addChangeListener(this);
347 add(zoomSlider, BorderLayout.PAGE_END);
348 }
349
350 private void zoom(int amount) {
351 if (zoomSlider == null || amount == 0) return;
352
353 zoomSlider.setValue(zoomSlider.getValue() + (10 * (amount < 0 ? 1 : -1)));
354
355 }
356 }