From: Niki Roo Date: Thu, 7 May 2020 17:29:55 +0000 (+0200) Subject: new: zoombox X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=ac0e8fe448b842a043f2e265461d8496dae01fc1;p=nikiroo-utils.git new: zoombox --- diff --git a/ui/ZoomBox.java b/ui/ZoomBox.java new file mode 100644 index 0000000..44338d9 --- /dev/null +++ b/ui/ZoomBox.java @@ -0,0 +1,461 @@ +package be.nikiroo.utils.ui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BoxLayout; +import javax.swing.DefaultComboBoxModel; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComboBox; + +/** + * A small panel that let you choose a zoom level or an actual zoom value (when + * there is enough space to allow that). + * + * @author niki + */ +public class ZoomBox extends ListenerPanel { + private static final long serialVersionUID = 1L; + + /** The event that is fired on zoom change. */ + public static final String ZOOM_CHANGED = "zoom_changed"; + + private enum ZoomLevel { + FIT_TO_WIDTH(0, true), // + FIT_TO_HEIGHT(0, false), // + ACTUAL_SIZE(1, null), // + HALF_SIZE(0.5, null), // + DOUBLE_SIZE(2, null),// + ; + + private final double zoom; + private final Boolean snapMode; + + private ZoomLevel(double zoom, Boolean snapMode) { + this.zoom = zoom; + this.snapMode = snapMode; + } + + public double getZoom() { + return zoom; + } + + public Boolean getSnapToWidth() { + return snapMode; + } + + /** + * Use default values that can be understood by a human. + */ + @Override + public String toString() { + switch (this) { + case FIT_TO_WIDTH: + return "Fit to width"; + case FIT_TO_HEIGHT: + return "Fit to height"; + case ACTUAL_SIZE: + return "Actual size"; + case HALF_SIZE: + return "Half size"; + case DOUBLE_SIZE: + return "Double size"; + } + return super.toString(); + } + + static ZoomLevel[] values(boolean orderedSelection) { + if (orderedSelection) { + return new ZoomLevel[] { // + FIT_TO_WIDTH, // + FIT_TO_HEIGHT, // + ACTUAL_SIZE, // + HALF_SIZE, // + DOUBLE_SIZE,// + }; + } + + return values(); + } + } + + private boolean vertical; + private boolean small; + + private JButton zoomIn; + private JButton zoomOut; + private JButton snapWidth; + private JButton snapHeight; + + @SuppressWarnings("rawtypes") // JComboBox is not java 1.6 compatible + private JComboBox zoombox; + + private double zoom = 1; + private Boolean snapMode = true; + + @SuppressWarnings("rawtypes") // JComboBox not compatible java 1.6 + private DefaultComboBoxModel zoomBoxModel; + + /** + * Create a new {@link ZoomBox}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) // JComboBox not + // compatible java 1.6 + public ZoomBox() { + zoomIn = new JButton(); + zoomIn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + zoomIn(1); + } + }); + + zoomBoxModel = new DefaultComboBoxModel(ZoomLevel.values(true)); + zoombox = new JComboBox(zoomBoxModel); + zoombox.setEditable(true); + zoombox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Object selected = zoomBoxModel.getSelectedItem(); + + if (selected == null) { + return; + } + + if (selected instanceof ZoomLevel) { + ZoomLevel selectedZoomLevel = (ZoomLevel) selected; + setZoomSnapMode(selectedZoomLevel.getZoom(), + selectedZoomLevel.getSnapToWidth()); + } else { + String selectedString = selected.toString(); + selectedString = selectedString.trim(); + if (selectedString.endsWith("%")) { + selectedString = selectedString + .substring(0, selectedString.length() - 1) + .trim(); + } + + try { + int pc = Integer.parseInt(selectedString); + if (pc <= 0) { + throw new NumberFormatException("invalid"); + } + + setZoomSnapMode(pc / 100.0, null); + } catch (NumberFormatException nfe) { + } + } + + fireActionPerformed(ZOOM_CHANGED); + } + }); + + zoomOut = new JButton(); + zoomOut.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + zoomOut(1); + } + }); + + snapWidth = new JButton(); + snapWidth.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setSnapMode(true); + } + }); + + snapHeight = new JButton(); + snapHeight.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setSnapMode(false); + } + }); + + setIcons(null, null, null, null); + setOrientation(vertical); + } + + /** + * The zoom level. + *

+ * It usually returns 1 (default value), the value you passed yourself or 0 + * (a snap to width or snap to height was asked by the user). + *

+ * Will cause a fire event if needed. + * + * @param zoom + * the zoom level + */ + public void setZoom(double zoom) { + if (this.zoom != zoom) { + doSetZoom(zoom); + fireActionPerformed(ZOOM_CHANGED); + } + } + + /** + * The snap mode (NULL means no snap mode, TRUE for snap to width, FALSE for + * snap to height). + *

+ * Will cause a fire event if needed. + * + * @param snapToWidth + * the snap mode + */ + public void setSnapMode(Boolean snapToWidth) { + if (this.snapMode != snapToWidth) { + doSetSnapMode(snapToWidth); + fireActionPerformed(ZOOM_CHANGED); + } + } + + /** + * Set both {@link ZoomBox#setZoom(double)} and + * {@link ZoomBox#setSnapMode(Boolean)} but fire only one change event. + *

+ * Will cause a fire event if needed. + * + * @param zoom + * the zoom level + * @param snapMode + * the snap mode + */ + public void setZoomSnapMode(double zoom, Boolean snapMode) { + if (this.zoom != zoom || this.snapMode != snapMode) { + doSetZoom(zoom); + doSetSnapMode(snapMode); + fireActionPerformed(ZOOM_CHANGED); + } + } + + /** + * The zoom level. + *

+ * It usually returns 1 (default value), the value you passed yourself or 0 + * (a snap to width or snap to height was asked by the user). + * + * @return the zoom level + */ + public double getZoom() { + return zoom; + } + + /** + * The snap mode (NULL means no snap mode, TRUE for snap to width, FALSE for + * snap to height). + * + * @return the snap mode + */ + public Boolean getSnapMode() { + return snapMode; + } + + /** + * Zoom in, by a certain amount in "steps". + *

+ * Note that zoomIn(-1) is the same as zoomOut(1). + * + * @param steps + * the number of zoom steps to make, can be negative + */ + public void zoomIn(int steps) { + // TODO: redo zoomIn/zoomOut correctly + if (steps < 0) { + zoomOut(-steps); + return; + } + + double newZoom = zoom; + for (int i = 0; i < steps; i++) { + newZoom = newZoom + (newZoom < 0.1 ? 0.01 : 0.1); + if (newZoom > 0.1) { + newZoom = Math.round(newZoom * 10.0) / 10.0; // snap to 10% + } else { + newZoom = Math.round(newZoom * 100.0) / 100.0; // snap to 1% + } + } + + setZoomSnapMode(newZoom, null); + fireActionPerformed(ZOOM_CHANGED); + } + + /** + * Zoom out, by a certain amount in "steps". + *

+ * Note that zoomOut(-1) is the same as zoomIn(1). + * + * @param steps + * the number of zoom steps to make, can be negative + */ + public void zoomOut(int steps) { + if (steps < 0) { + zoomIn(-steps); + return; + } + + double newZoom = zoom; + for (int i = 0; i < steps; i++) { + newZoom = newZoom - (newZoom > 0.19 ? 0.1 : 0.01); + if (newZoom < 0.01) { + newZoom = 0.01; + break; + } + + if (newZoom > 0.1) { + newZoom = Math.round(newZoom * 10.0) / 10.0; // snap to 10% + } else { + newZoom = Math.round(newZoom * 100.0) / 100.0; // snap to 1% + } + } + + setZoomSnapMode(newZoom, null); + fireActionPerformed(ZOOM_CHANGED); + } + + /** + * Set icons for the buttons instead of square brackets. + *

+ * Any NULL value will make the button use square brackets again. + * + * @param zoomIn + * the icon of the button "go to first page" + * @param zoomOut + * the icon of the button "go to previous page" + * @param snapWidth + * the icon of the button "go to next page" + * @param snapHeight + * the icon of the button "go to last page" + */ + public void setIcons(Icon zoomIn, Icon zoomOut, Icon snapWidth, + Icon snapHeight) { + this.zoomIn.setIcon(zoomIn); + this.zoomIn.setText(zoomIn == null ? "+" : ""); + this.zoomOut.setIcon(zoomOut); + this.zoomOut.setText(zoomOut == null ? "-" : ""); + this.snapWidth.setIcon(snapWidth); + this.snapWidth.setText(snapWidth == null ? "W" : ""); + this.snapHeight.setIcon(snapHeight); + this.snapHeight.setText(snapHeight == null ? "H" : ""); + } + + /** + * A smaller {@link ZoomBox} that uses buttons instead of a big combo box + * for the zoom modes. + *

+ * Always small in vertical orientation. + * + * @return TRUE if it is small + */ + public boolean getSmall() { + return small; + } + + /** + * A smaller {@link ZoomBox} that uses buttons instead of a big combo box + * for the zoom modes. + *

+ * Always small in vertical orientation. + * + * @param small + * TRUE to set it small + * + * @return TRUE if it changed something + */ + public boolean setSmall(boolean small) { + return setUi(small, vertical); + } + + /** + * The general orientation of the component. + * + * @return TRUE for vertical orientation, FALSE for horisontal orientation + */ + public boolean getOrientation() { + return vertical; + } + + /** + * The general orientation of the component. + * + * @param vertical + * TRUE for vertical orientation, FALSE for horisontal + * orientation + * + * @return TRUE if it changed something + */ + public boolean setOrientation(boolean vertical) { + return setUi(small, vertical); + } + + /** + * Set the zoom level, no fire event. + *

+ * It usually returns 1 (default value), the value you passed yourself or 0 + * (a snap to width or snap to height was asked by the user). + * + * @param zoom + * the zoom level + */ + private void doSetZoom(double zoom) { + if (snapMode == null) { + zoomBoxModel.setSelectedItem( + Integer.toString((int) Math.round(zoom * 100)) + " %"); + } + + this.zoom = zoom; + } + + /** + * Set the snap mode, no fire event. + * + * @param snapToWidth + * the snap mode + */ + private void doSetSnapMode(Boolean snapToWidth) { + if (snapToWidth == null) { + zoomBoxModel.setSelectedItem( + Integer.toString((int) Math.round(zoom * 100)) + " %"); + } else { + for (ZoomLevel level : ZoomLevel.values()) { + if (level.getSnapToWidth() == snapToWidth) { + zoomBoxModel.setSelectedItem(level); + } + } + } + + this.snapMode = snapToWidth; + } + + private boolean setUi(boolean small, boolean vertical) { + if (getWidth() == 0 || this.small != small + || this.vertical != vertical) { + this.small = small; + this.vertical = vertical; + + BoxLayout layout = new BoxLayout(this, + vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS); + this.removeAll(); + setLayout(layout); + + this.add(zoomIn); + if (vertical || small) { + this.add(snapWidth); + this.add(snapHeight); + } else { + this.add(zoombox); + } + this.add(zoomOut); + + this.revalidate(); + this.repaint(); + + return true; + } + + return false; + } +}