Commit | Line | Data |
---|---|---|
ac0e8fe4 NR |
1 | package be.nikiroo.utils.ui; |
2 | ||
3 | import java.awt.event.ActionEvent; | |
4 | import java.awt.event.ActionListener; | |
5 | ||
57903517 | 6 | import javax.swing.BorderFactory; |
ac0e8fe4 NR |
7 | import javax.swing.BoxLayout; |
8 | import javax.swing.DefaultComboBoxModel; | |
9 | import javax.swing.Icon; | |
10 | import javax.swing.JButton; | |
11 | import javax.swing.JComboBox; | |
deb87cca | 12 | import javax.swing.JLabel; |
ac0e8fe4 NR |
13 | |
14 | /** | |
15 | * A small panel that let you choose a zoom level or an actual zoom value (when | |
16 | * there is enough space to allow that). | |
17 | * | |
18 | * @author niki | |
19 | */ | |
20 | public class ZoomBox extends ListenerPanel { | |
21 | private static final long serialVersionUID = 1L; | |
22 | ||
23 | /** The event that is fired on zoom change. */ | |
24 | public static final String ZOOM_CHANGED = "zoom_changed"; | |
25 | ||
26 | private enum ZoomLevel { | |
27 | FIT_TO_WIDTH(0, true), // | |
28 | FIT_TO_HEIGHT(0, false), // | |
29 | ACTUAL_SIZE(1, null), // | |
30 | HALF_SIZE(0.5, null), // | |
31 | DOUBLE_SIZE(2, null),// | |
32 | ; | |
33 | ||
34 | private final double zoom; | |
35 | private final Boolean snapMode; | |
36 | ||
37 | private ZoomLevel(double zoom, Boolean snapMode) { | |
38 | this.zoom = zoom; | |
39 | this.snapMode = snapMode; | |
40 | } | |
41 | ||
42 | public double getZoom() { | |
43 | return zoom; | |
44 | } | |
45 | ||
46 | public Boolean getSnapToWidth() { | |
47 | return snapMode; | |
48 | } | |
49 | ||
50 | /** | |
51 | * Use default values that can be understood by a human. | |
52 | */ | |
53 | @Override | |
54 | public String toString() { | |
55 | switch (this) { | |
56 | case FIT_TO_WIDTH: | |
57 | return "Fit to width"; | |
58 | case FIT_TO_HEIGHT: | |
59 | return "Fit to height"; | |
60 | case ACTUAL_SIZE: | |
61 | return "Actual size"; | |
62 | case HALF_SIZE: | |
63 | return "Half size"; | |
64 | case DOUBLE_SIZE: | |
65 | return "Double size"; | |
66 | } | |
67 | return super.toString(); | |
68 | } | |
69 | ||
70 | static ZoomLevel[] values(boolean orderedSelection) { | |
71 | if (orderedSelection) { | |
72 | return new ZoomLevel[] { // | |
73 | FIT_TO_WIDTH, // | |
74 | FIT_TO_HEIGHT, // | |
75 | ACTUAL_SIZE, // | |
76 | HALF_SIZE, // | |
77 | DOUBLE_SIZE,// | |
78 | }; | |
79 | } | |
80 | ||
81 | return values(); | |
82 | } | |
83 | } | |
84 | ||
85 | private boolean vertical; | |
86 | private boolean small; | |
87 | ||
88 | private JButton zoomIn; | |
89 | private JButton zoomOut; | |
90 | private JButton snapWidth; | |
91 | private JButton snapHeight; | |
deb87cca | 92 | private JLabel zoomLabel; |
ac0e8fe4 NR |
93 | |
94 | @SuppressWarnings("rawtypes") // JComboBox<?> is not java 1.6 compatible | |
95 | private JComboBox zoombox; | |
96 | ||
97 | private double zoom = 1; | |
98 | private Boolean snapMode = true; | |
99 | ||
100 | @SuppressWarnings("rawtypes") // JComboBox<?> not compatible java 1.6 | |
101 | private DefaultComboBoxModel zoomBoxModel; | |
102 | ||
103 | /** | |
104 | * Create a new {@link ZoomBox}. | |
105 | */ | |
106 | @SuppressWarnings({ "unchecked", "rawtypes" }) // JComboBox<?> not | |
107 | // compatible java 1.6 | |
108 | public ZoomBox() { | |
109 | zoomIn = new JButton(); | |
110 | zoomIn.addActionListener(new ActionListener() { | |
111 | @Override | |
112 | public void actionPerformed(ActionEvent e) { | |
113 | zoomIn(1); | |
114 | } | |
115 | }); | |
116 | ||
117 | zoomBoxModel = new DefaultComboBoxModel(ZoomLevel.values(true)); | |
118 | zoombox = new JComboBox(zoomBoxModel); | |
119 | zoombox.setEditable(true); | |
120 | zoombox.addActionListener(new ActionListener() { | |
121 | @Override | |
122 | public void actionPerformed(ActionEvent e) { | |
123 | Object selected = zoomBoxModel.getSelectedItem(); | |
124 | ||
125 | if (selected == null) { | |
126 | return; | |
127 | } | |
128 | ||
129 | if (selected instanceof ZoomLevel) { | |
130 | ZoomLevel selectedZoomLevel = (ZoomLevel) selected; | |
131 | setZoomSnapMode(selectedZoomLevel.getZoom(), | |
132 | selectedZoomLevel.getSnapToWidth()); | |
133 | } else { | |
134 | String selectedString = selected.toString(); | |
135 | selectedString = selectedString.trim(); | |
136 | if (selectedString.endsWith("%")) { | |
137 | selectedString = selectedString | |
138 | .substring(0, selectedString.length() - 1) | |
139 | .trim(); | |
140 | } | |
141 | ||
142 | try { | |
143 | int pc = Integer.parseInt(selectedString); | |
144 | if (pc <= 0) { | |
145 | throw new NumberFormatException("invalid"); | |
146 | } | |
147 | ||
148 | setZoomSnapMode(pc / 100.0, null); | |
149 | } catch (NumberFormatException nfe) { | |
150 | } | |
151 | } | |
152 | ||
153 | fireActionPerformed(ZOOM_CHANGED); | |
154 | } | |
155 | }); | |
156 | ||
157 | zoomOut = new JButton(); | |
158 | zoomOut.addActionListener(new ActionListener() { | |
159 | @Override | |
160 | public void actionPerformed(ActionEvent e) { | |
161 | zoomOut(1); | |
162 | } | |
163 | }); | |
164 | ||
165 | snapWidth = new JButton(); | |
166 | snapWidth.addActionListener(new ActionListener() { | |
167 | @Override | |
168 | public void actionPerformed(ActionEvent e) { | |
169 | setSnapMode(true); | |
170 | } | |
171 | }); | |
172 | ||
173 | snapHeight = new JButton(); | |
174 | snapHeight.addActionListener(new ActionListener() { | |
175 | @Override | |
176 | public void actionPerformed(ActionEvent e) { | |
177 | setSnapMode(false); | |
178 | } | |
179 | }); | |
180 | ||
deb87cca | 181 | zoomLabel = new JLabel(); |
7ce18848 | 182 | zoomLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 0)); |
deb87cca | 183 | |
ac0e8fe4 NR |
184 | setIcons(null, null, null, null); |
185 | setOrientation(vertical); | |
186 | } | |
187 | ||
188 | /** | |
189 | * The zoom level. | |
190 | * <p> | |
deb87cca | 191 | * It usually returns 1 (default value), the value you passed yourself or 1 |
ac0e8fe4 NR |
192 | * (a snap to width or snap to height was asked by the user). |
193 | * <p> | |
194 | * Will cause a fire event if needed. | |
195 | * | |
196 | * @param zoom | |
197 | * the zoom level | |
198 | */ | |
199 | public void setZoom(double zoom) { | |
200 | if (this.zoom != zoom) { | |
201 | doSetZoom(zoom); | |
202 | fireActionPerformed(ZOOM_CHANGED); | |
203 | } | |
204 | } | |
205 | ||
206 | /** | |
207 | * The snap mode (NULL means no snap mode, TRUE for snap to width, FALSE for | |
208 | * snap to height). | |
209 | * <p> | |
210 | * Will cause a fire event if needed. | |
211 | * | |
212 | * @param snapToWidth | |
213 | * the snap mode | |
214 | */ | |
215 | public void setSnapMode(Boolean snapToWidth) { | |
216 | if (this.snapMode != snapToWidth) { | |
217 | doSetSnapMode(snapToWidth); | |
218 | fireActionPerformed(ZOOM_CHANGED); | |
219 | } | |
220 | } | |
221 | ||
222 | /** | |
223 | * Set both {@link ZoomBox#setZoom(double)} and | |
224 | * {@link ZoomBox#setSnapMode(Boolean)} but fire only one change event. | |
225 | * <p> | |
226 | * Will cause a fire event if needed. | |
227 | * | |
228 | * @param zoom | |
229 | * the zoom level | |
230 | * @param snapMode | |
231 | * the snap mode | |
232 | */ | |
233 | public void setZoomSnapMode(double zoom, Boolean snapMode) { | |
234 | if (this.zoom != zoom || this.snapMode != snapMode) { | |
235 | doSetZoom(zoom); | |
236 | doSetSnapMode(snapMode); | |
237 | fireActionPerformed(ZOOM_CHANGED); | |
238 | } | |
239 | } | |
240 | ||
241 | /** | |
242 | * The zoom level. | |
243 | * <p> | |
244 | * It usually returns 1 (default value), the value you passed yourself or 0 | |
245 | * (a snap to width or snap to height was asked by the user). | |
246 | * | |
247 | * @return the zoom level | |
248 | */ | |
249 | public double getZoom() { | |
250 | return zoom; | |
251 | } | |
252 | ||
253 | /** | |
254 | * The snap mode (NULL means no snap mode, TRUE for snap to width, FALSE for | |
255 | * snap to height). | |
256 | * | |
257 | * @return the snap mode | |
258 | */ | |
259 | public Boolean getSnapMode() { | |
260 | return snapMode; | |
261 | } | |
262 | ||
263 | /** | |
264 | * Zoom in, by a certain amount in "steps". | |
265 | * <p> | |
266 | * Note that zoomIn(-1) is the same as zoomOut(1). | |
267 | * | |
268 | * @param steps | |
269 | * the number of zoom steps to make, can be negative | |
270 | */ | |
271 | public void zoomIn(int steps) { | |
272 | // TODO: redo zoomIn/zoomOut correctly | |
273 | if (steps < 0) { | |
274 | zoomOut(-steps); | |
275 | return; | |
276 | } | |
277 | ||
278 | double newZoom = zoom; | |
279 | for (int i = 0; i < steps; i++) { | |
280 | newZoom = newZoom + (newZoom < 0.1 ? 0.01 : 0.1); | |
281 | if (newZoom > 0.1) { | |
282 | newZoom = Math.round(newZoom * 10.0) / 10.0; // snap to 10% | |
283 | } else { | |
284 | newZoom = Math.round(newZoom * 100.0) / 100.0; // snap to 1% | |
285 | } | |
286 | } | |
287 | ||
288 | setZoomSnapMode(newZoom, null); | |
289 | fireActionPerformed(ZOOM_CHANGED); | |
290 | } | |
291 | ||
292 | /** | |
293 | * Zoom out, by a certain amount in "steps". | |
294 | * <p> | |
295 | * Note that zoomOut(-1) is the same as zoomIn(1). | |
296 | * | |
297 | * @param steps | |
298 | * the number of zoom steps to make, can be negative | |
299 | */ | |
300 | public void zoomOut(int steps) { | |
301 | if (steps < 0) { | |
302 | zoomIn(-steps); | |
303 | return; | |
304 | } | |
305 | ||
306 | double newZoom = zoom; | |
307 | for (int i = 0; i < steps; i++) { | |
308 | newZoom = newZoom - (newZoom > 0.19 ? 0.1 : 0.01); | |
309 | if (newZoom < 0.01) { | |
310 | newZoom = 0.01; | |
311 | break; | |
312 | } | |
313 | ||
314 | if (newZoom > 0.1) { | |
315 | newZoom = Math.round(newZoom * 10.0) / 10.0; // snap to 10% | |
316 | } else { | |
317 | newZoom = Math.round(newZoom * 100.0) / 100.0; // snap to 1% | |
318 | } | |
319 | } | |
320 | ||
321 | setZoomSnapMode(newZoom, null); | |
322 | fireActionPerformed(ZOOM_CHANGED); | |
323 | } | |
324 | ||
325 | /** | |
326 | * Set icons for the buttons instead of square brackets. | |
327 | * <p> | |
328 | * Any NULL value will make the button use square brackets again. | |
329 | * | |
330 | * @param zoomIn | |
331 | * the icon of the button "go to first page" | |
332 | * @param zoomOut | |
333 | * the icon of the button "go to previous page" | |
334 | * @param snapWidth | |
335 | * the icon of the button "go to next page" | |
336 | * @param snapHeight | |
337 | * the icon of the button "go to last page" | |
338 | */ | |
339 | public void setIcons(Icon zoomIn, Icon zoomOut, Icon snapWidth, | |
340 | Icon snapHeight) { | |
341 | this.zoomIn.setIcon(zoomIn); | |
342 | this.zoomIn.setText(zoomIn == null ? "+" : ""); | |
343 | this.zoomOut.setIcon(zoomOut); | |
344 | this.zoomOut.setText(zoomOut == null ? "-" : ""); | |
345 | this.snapWidth.setIcon(snapWidth); | |
346 | this.snapWidth.setText(snapWidth == null ? "W" : ""); | |
347 | this.snapHeight.setIcon(snapHeight); | |
348 | this.snapHeight.setText(snapHeight == null ? "H" : ""); | |
349 | } | |
350 | ||
351 | /** | |
352 | * A smaller {@link ZoomBox} that uses buttons instead of a big combo box | |
353 | * for the zoom modes. | |
354 | * <p> | |
355 | * Always small in vertical orientation. | |
356 | * | |
357 | * @return TRUE if it is small | |
358 | */ | |
359 | public boolean getSmall() { | |
360 | return small; | |
361 | } | |
362 | ||
363 | /** | |
364 | * A smaller {@link ZoomBox} that uses buttons instead of a big combo box | |
365 | * for the zoom modes. | |
366 | * <p> | |
367 | * Always small in vertical orientation. | |
368 | * | |
369 | * @param small | |
370 | * TRUE to set it small | |
371 | * | |
372 | * @return TRUE if it changed something | |
373 | */ | |
374 | public boolean setSmall(boolean small) { | |
375 | return setUi(small, vertical); | |
376 | } | |
377 | ||
378 | /** | |
379 | * The general orientation of the component. | |
380 | * | |
381 | * @return TRUE for vertical orientation, FALSE for horisontal orientation | |
382 | */ | |
383 | public boolean getOrientation() { | |
384 | return vertical; | |
385 | } | |
386 | ||
387 | /** | |
388 | * The general orientation of the component. | |
389 | * | |
390 | * @param vertical | |
391 | * TRUE for vertical orientation, FALSE for horisontal | |
392 | * orientation | |
393 | * | |
394 | * @return TRUE if it changed something | |
395 | */ | |
396 | public boolean setOrientation(boolean vertical) { | |
397 | return setUi(small, vertical); | |
398 | } | |
399 | ||
400 | /** | |
401 | * Set the zoom level, no fire event. | |
402 | * <p> | |
403 | * It usually returns 1 (default value), the value you passed yourself or 0 | |
404 | * (a snap to width or snap to height was asked by the user). | |
405 | * | |
406 | * @param zoom | |
407 | * the zoom level | |
408 | */ | |
409 | private void doSetZoom(double zoom) { | |
deb87cca NR |
410 | if (zoom > 0) { |
411 | String zoomStr = Integer.toString((int) Math.round(zoom * 100)) | |
412 | + " %"; | |
413 | zoomLabel.setText(zoomStr); | |
414 | if (snapMode == null) { | |
415 | zoomBoxModel.setSelectedItem(zoomStr); | |
416 | } | |
ac0e8fe4 NR |
417 | } |
418 | ||
419 | this.zoom = zoom; | |
420 | } | |
421 | ||
422 | /** | |
423 | * Set the snap mode, no fire event. | |
424 | * | |
425 | * @param snapToWidth | |
426 | * the snap mode | |
427 | */ | |
428 | private void doSetSnapMode(Boolean snapToWidth) { | |
429 | if (snapToWidth == null) { | |
deb87cca NR |
430 | String zoomStr = Integer.toString((int) Math.round(zoom * 100)) |
431 | + " %"; | |
432 | if (zoom > 0) { | |
433 | zoomBoxModel.setSelectedItem(zoomStr); | |
434 | } | |
ac0e8fe4 NR |
435 | } else { |
436 | for (ZoomLevel level : ZoomLevel.values()) { | |
437 | if (level.getSnapToWidth() == snapToWidth) { | |
438 | zoomBoxModel.setSelectedItem(level); | |
439 | } | |
440 | } | |
441 | } | |
442 | ||
443 | this.snapMode = snapToWidth; | |
444 | } | |
445 | ||
446 | private boolean setUi(boolean small, boolean vertical) { | |
447 | if (getWidth() == 0 || this.small != small | |
448 | || this.vertical != vertical) { | |
449 | this.small = small; | |
450 | this.vertical = vertical; | |
451 | ||
452 | BoxLayout layout = new BoxLayout(this, | |
453 | vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS); | |
454 | this.removeAll(); | |
455 | setLayout(layout); | |
456 | ||
ac0e8fe4 | 457 | if (vertical || small) { |
deb87cca | 458 | this.add(zoomIn); |
ac0e8fe4 NR |
459 | this.add(snapWidth); |
460 | this.add(snapHeight); | |
deb87cca NR |
461 | this.add(zoomOut); |
462 | this.add(zoomLabel); | |
ac0e8fe4 | 463 | } else { |
deb87cca | 464 | this.add(zoomIn); |
ac0e8fe4 | 465 | this.add(zoombox); |
deb87cca | 466 | this.add(zoomOut); |
ac0e8fe4 | 467 | } |
ac0e8fe4 NR |
468 | |
469 | this.revalidate(); | |
470 | this.repaint(); | |
471 | ||
472 | return true; | |
473 | } | |
474 | ||
475 | return false; | |
476 | } | |
477 | } |