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