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