/******************************************************************************* * Copyright (c) 2010 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ package org.csstudio.swt.xygraph.figures; import java.util.ArrayList; import java.util.List; import org.csstudio.swt.xygraph.dataprovider.IDataProvider; import org.csstudio.swt.xygraph.linearscale.LinearScale; import org.csstudio.swt.xygraph.linearscale.Range; import org.csstudio.swt.xygraph.undo.AxisPanOrZoomCommand; import org.csstudio.swt.xygraph.undo.SaveStateCommand; import org.csstudio.swt.xygraph.undo.ZoomType; import org.csstudio.swt.xygraph.util.GraphicsUtil; import org.csstudio.swt.xygraph.util.Log10; import org.csstudio.swt.xygraph.util.SWTConstants; import org.csstudio.swt.xygraph.util.XYGraphMediaFactory; import org.csstudio.swt.xygraph.util.XYGraphMediaFactory.CURSOR_TYPE; import org.eclipse.draw2d.FigureUtilities; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.MouseEvent; import org.eclipse.draw2d.MouseListener; import org.eclipse.draw2d.MouseMotionListener; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; /** * The axis figure. * * @author Xihui Chen * @author Kay Kasemir - Axis zoom/pan tweaks */ public class Axis extends LinearScale{ /** The ratio of the shrink/expand area for one zoom. */ final static double ZOOM_RATIO = 0.1; /** The auto zoom interval in ms.*/ final static int ZOOM_SPEED = 200; // private static final Color GRAY_COLOR = XYGraphMediaFactory.getInstance().getColor( // XYGraphMediaFactory.COLOR_GRAY); private String title; @Override public void setFont(Font font) { // TODO Auto-generated method stub super.setFont(font); this.scaleFontData = getFont().getFontData()[0]; } final private List<Trace> traceList = new ArrayList<Trace>(); private XYGraph xyGraph; private Grid grid; private Font titleFont; //title FontData : Add because of SWT illegal thread access private FontData titleFontData; //title FontData : Add because of SWT illegal thread access private FontData scaleFontData; private boolean autoScale = false; private boolean showMajorGrid = false; private boolean showMinorGrid = false; private Color majorGridColor; private Color minorGridColor; private boolean dashGridLine = true; private double autoScaleThreshold = 0.01; final private List<IAxisListener> listeners = new ArrayList<IAxisListener>(); private ZoomType zoomType = ZoomType.NONE; private Point start; private Point end; private boolean armed; private Range startRange; private Cursor grabbing; private Color revertBackColor; private RGB colorRGB; private RGB majorGridColorRGB; public FontData getTitleFontData() { return titleFontData; } public FontData getScaleFontData() { return scaleFontData; } /**Constructor * @param title title of the axis * @param yAxis true if this is the Y-Axis, false if this is the X-Axis. */ public Axis(final String title, final boolean yAxis) { super(); this.title = title; if(yAxis) setOrientation(Orientation.VERTICAL); //Save drawing line operation in RAP. if(GraphicsUtil.isRAP()) setMinorTicksVisible(false); final AxisMouseListener panner = new AxisMouseListener(); addMouseListener(panner); addMouseMotionListener(panner); grabbing = XYGraphMediaFactory.getCursor(CURSOR_TYPE.GRABBING); Font sysFont = Display.getCurrent().getSystemFont(); titleFont = XYGraphMediaFactory.getInstance().getFont( new FontData(sysFont.getFontData()[0].getName(), 12, SWT.BOLD)); //$NON-NLS-1$ if(getBackgroundColor() != null){ RGB backRGB = getBackgroundColor().getRGB(); revertBackColor = XYGraphMediaFactory.getInstance().getColor(255- backRGB.red, 255 - backRGB.green, 255 - backRGB.blue); }else revertBackColor = XYGraphMediaFactory.getInstance().getColor(100,100,100); } public void addListener(final IAxisListener listener){ if(listeners.contains(listener)) return; listeners.add(listener); } public boolean removeListenr(final IAxisListener listener){ return listeners.remove(listener); } private void fireRevalidated(){ for(IAxisListener listener : listeners) listener.axisRevalidated(this); } private void fireAxisRangeChanged(final Range old_range, final Range new_range){ for(IAxisListener listener : listeners) listener.axisRangeChanged(this, old_range, new_range); } @Override public void setRange(final double lower, final double upper) { if (Orientation.VERTICAL == getOrientation() && autoScale == false) return; //scouter.project 20150910 Range old_range = getRange(); super.setRange(lower, upper); fireAxisRangeChanged(old_range, getRange()); } public void setRangeDirect(final double lower, final double upper) { Range old_range = getRange(); super.setRange(lower, upper); fireAxisRangeChanged(old_range, getRange()); } @Override protected void layout() { super.layout(); fireRevalidated(); } @Override public void setVisible(boolean visible) { super.setVisible(visible); revalidate(); } @Override public void setForegroundColor(final Color color) { Color oldColor = getForegroundColor(); super.setForegroundColor(color); colorRGB = color.getRGB(); if(xyGraph != null) xyGraph.repaint(); fireAxisForegroundColorChanged(oldColor, color); } @Override public void setMinorTicksVisible(boolean minorTicksVisible) { //Save line operation in RAP. if(GraphicsUtil.isRAP()) super.setMinorTicksVisible(false); else super.setMinorTicksVisible(minorTicksVisible); } public RGB getForegroundColorRGB(){ return colorRGB; } private void fireAxisForegroundColorChanged(Color oldColor, Color newColor) { for(IAxisListener listener : listeners) listener.axisForegroundColorChanged(this, oldColor, newColor); } @Override public void setBackgroundColor(Color bg) { RGB backRGB = bg.getRGB(); revertBackColor = XYGraphMediaFactory.getInstance().getColor(255- backRGB.red, 255 - backRGB.green, 255 - backRGB.blue); super.setBackgroundColor(bg); } /** * Edited by scouter.project@gmail.com * Add case that no title axis */ @Override public Dimension getPreferredSize(final int wHint, final int hHint) { final Dimension d = super.getPreferredSize(wHint, hHint); if (isVisible()) { if (title.length() > 0) { if (isHorizontal()) d.height += FigureUtilities.getTextExtents(title, titleFont).height; else d.width += FigureUtilities.getTextExtents(title, titleFont).height; } else { if (isHorizontal()) d.height += 2; else d.width += 5; } } else { // Not visible, flatten it to use zero height resp. width if (isHorizontal()) d.height = 0; else d.width = 0; } return d; } @Override protected void paintClientArea(final Graphics graphics) { // Don't do anything when hidden if (!isVisible()) return; super.paintClientArea(graphics); // graphics.pushState(); graphics.setFont(titleFont); final Dimension titleSize = FigureUtilities.getTextExtents(title, titleFont); if(isHorizontal()){ if(getTickLablesSide() == LabelSide.Primary) graphics.drawText(title, bounds.x + bounds.width/2 - titleSize.width/2, bounds.y + bounds.height - titleSize.height); else graphics.drawText(title, bounds.x + bounds.width/2 - titleSize.width/2, bounds.y); }else{ final int w = titleSize.height; final int h = titleSize.width +1; if(getTickLablesSide() == LabelSide.Primary){ GraphicsUtil.drawVerticalText(graphics, title, bounds.x, bounds.y + bounds.height/2 - h/2, false); }else { GraphicsUtil.drawVerticalText(graphics, title, bounds.x + bounds.width - w, bounds.y + bounds.height/2 - h/2, true); } } // graphics.popState(); // Show the start/end cursor or the 'rubberband' of a zoom operation? if(armed && end != null && start != null){ switch (zoomType) { case RUBBERBAND_ZOOM: case HORIZONTAL_ZOOM: case VERTICAL_ZOOM: graphics.setLineStyle(SWTConstants.LINE_DOT); graphics.setLineWidth(1); graphics.setForegroundColor(revertBackColor); graphics.drawRectangle(start.x, start.y, end.x - start.x-1, end.y - start.y-1); break; default: break; } } } /** @return Range that reflects the minimum and maximum value of all * traces on this axis. * Returns <code>null</code> if there is no trace data. */ public Range getTraceDataRange() { double low = Double.POSITIVE_INFINITY; double high = Double.NEGATIVE_INFINITY; for (Trace trace : traceList) { if (trace.getDataProvider() == null || !trace.isVisible()) continue; final Range range; if (isHorizontal()) range = trace.getDataProvider().getXDataMinMax(); else range = trace.getDataProvider().getYDataMinMax(); if (range == null) continue; if (Double.isInfinite(range.getLower()) || Double.isInfinite(range.getUpper()) || Double.isNaN(range.getLower()) || Double.isNaN(range.getUpper())) continue; if (low > range.getLower()) low = range.getLower(); if (high < range.getUpper()) high = range.getUpper(); } if (Double.isInfinite(low) || Double.isInfinite(high)) return null; return new Range(low, high); } /** Perform an auto-scale: * Axis limits are set to the value range of the traces on this axis. * Includes some optimization: * Axis range is set a little wider than exact trace data range. * When auto-scale would only perform a minor axis adjustment, * axis is left unchanged. * * @param force If true, the axis will be auto-scaled by force regardless the autoScale field. * Otherwise, it will use the autoScale field to judge whether an auto-scale will be performed. * @return true if the axis is repainted due to range change. * * @see #autoScaleThreshold */ public boolean performAutoScale(final boolean force){ // Anything to do? Autoscale not enabled nor forced? if (traceList.size() <= 0 || !(force || autoScale)) return false; if(!force && xyGraph.getZoomType() != ZoomType.NONE) return false; // Get range of data in all traces final Range range = getTraceDataRange(); if (range == null) return false; double tempMin = range.getLower(); double tempMax = range.getUpper(); // Get current axis range, determine how 'different' they are double max = getRange().getUpper(); double min = getRange().getLower(); if (isLogScaleEnabled()) { // Transition into log space tempMin = Log10.log10(tempMin); tempMax = Log10.log10(tempMax); max = Log10.log10(max); min = Log10.log10(min); } //Give extra space in plot area // double extraSpace = (tempMax-tempMin)*0.05; // double tempMin2 = tempMin -extraSpace; // double tempMax2 = tempMax + extraSpace; final double thr = (max - min)*autoScaleThreshold; //if both the changes are lower than threshold, return if(((tempMin - min)>=0 && (tempMin - min)<thr) && ((max - tempMax)>=0 && (max - tempMax)<thr)){ return false; }else { //expand more space than needed if((tempMin - min)<0) tempMin -= thr; if((tempMax - max) > 0) tempMax += thr; } // if(tempMin>tempMin2) // tempMin=tempMin2; // if(tempMax<tempMax2) // tempMax = tempMax2; // Any change at all? if((Double.doubleToLongBits(tempMin) == Double.doubleToLongBits(min) && Double.doubleToLongBits(tempMax) == Double.doubleToLongBits(max)) || Double.isInfinite(tempMin) || Double.isInfinite(tempMax) || Double.isNaN(tempMin) || Double.isNaN(tempMax)) return false; if (isLogScaleEnabled()) { // Revert from log space tempMin = Log10.pow10(tempMin); tempMax = Log10.pow10(tempMax); } // Update axis setRange(tempMin, tempMax, true); repaint(); return true; } /**Add a trace to the axis. * @param trace the trace to be added. */ public void addTrace(final Trace trace){ if(traceList.contains(trace)) return; traceList.add(trace); performAutoScale(false); } /**Remove a trace from the axis. * @param trace * @return true if this axis contained the specified trace */ public boolean removeTrace(final Trace trace){ final boolean r = traceList.remove(trace); performAutoScale(false); return r; } /** * @param title the title to set */ public void setTitle(final String title) { String oldTitle = this.title; this.title = title; if(xyGraph != null) xyGraph.repaint(); fireAxisTitleChanged(oldTitle, title); } private void fireAxisTitleChanged(String oldTitle, String newTitle) { for(IAxisListener listener : listeners) listener.axisTitleChanged(this, oldTitle, newTitle); } /** * @return the title */ public String getTitle() { return title; } /** * @return the autoScale */ public boolean isAutoScale() { return autoScale; } /** * @param autoScale the autoScale to set */ public void setAutoScale(final boolean autoScale) { boolean oldAutoScale = this.autoScale; this.autoScale = autoScale; performAutoScale(false); fireAxisAutoScaleChanged(oldAutoScale, this.autoScale); } private void fireAxisAutoScaleChanged(boolean oldAutoScale, boolean newAutoScale) { for(IAxisListener listener : listeners) listener.axisAutoScaleChanged(this, oldAutoScale, newAutoScale); } /** * @return the showMajorGrid */ public boolean isShowMajorGrid() { return showMajorGrid; } /** * @param showMajorGrid the showMajorGrid to set */ public void setShowMajorGrid(final boolean showMajorGrid) { this.showMajorGrid = showMajorGrid; if(xyGraph != null) xyGraph.repaint(); } /** * @return the showMinorGrid */ public boolean isShowMinorGrid() { return showMinorGrid; } /** * @param showMinorGrid the showMinorGrid to set */ public void setShowMinorGrid(final boolean showMinorGrid) { this.showMinorGrid = showMinorGrid; if(xyGraph != null) xyGraph.repaint(); } /** * @return the majorGridColor */ public Color getMajorGridColor() { if(majorGridColor == null) majorGridColor = XYGraphMediaFactory.getInstance().getColor (XYGraphMediaFactory.COLOR_GRAY); return majorGridColor; } /** * @param majorGridColor the majorGridColor to set */ public void setMajorGridColor(final Color majorGridColor) { this.majorGridColor = majorGridColor; this.majorGridColorRGB = majorGridColor.getRGB(); if(xyGraph != null) xyGraph.repaint(); } public RGB getMajorGridColorRGB() { return majorGridColorRGB; } /** * @return the minorGridColor */ public Color getMinorGridColor() { if(minorGridColor == null) minorGridColor = XYGraphMediaFactory.getInstance().getColor (XYGraphMediaFactory.COLOR_GRAY); return minorGridColor; } /** * @param minorGridColor the minorGridColor to set */ public void setMinorGridColor(final Color minorGridColor) { this.minorGridColor = minorGridColor; if(xyGraph != null) xyGraph.repaint(); } /** * @param titleFont the titleFont to set */ public void setTitleFont(final Font titleFont) { this.titleFont = titleFont; this.titleFontData = titleFont.getFontData()[0]; repaint(); } /** * @return the dashGridLine */ public boolean isDashGridLine() { return dashGridLine; } /** * @param dashGridLine the dashGridLine to set */ public void setDashGridLine(final boolean dashGridLine) { this.dashGridLine = dashGridLine; if(xyGraph != null) xyGraph.repaint(); } /** * @param xyGraph the xyGraph to set */ public void setXyGraph(final XYGraph xyGraph) { this.xyGraph = xyGraph; } @Override public String toString() { return title; } public void dataChanged(final IDataProvider dataProvider) { if(autoScale) performAutoScale(false); } /**The autoScaleThreshold must be a value in range [0,1], which represents a percentage * of the plot area for the threshold when autoScale is performed.The autoScale will performed * only if the spare space exceeds this threshold. So it can reduce the CPU usage by * increasing the threshold. * @param autoScaleThreshold the autoScaleThreshold to set */ public void setAutoScaleThreshold(final double autoScaleThreshold) { if(autoScaleThreshold > 1 || autoScaleThreshold <0) throw new RuntimeException("The autoScaleThreshold must be a value in range [0,1]!"); //$NON-NLS-1$ this.autoScaleThreshold = autoScaleThreshold; } /** @param zoom Zoom Type * @return <code>true</code> if the zoom type is applicable to this axis */ private boolean isValidZoomType(final ZoomType zoom) { return zoom == ZoomType.PANNING || zoom == ZoomType.RUBBERBAND_ZOOM || zoom == ZoomType.ZOOM_IN || zoom == ZoomType.ZOOM_OUT || (isHorizontal() && (zoom == ZoomType.HORIZONTAL_ZOOM || zoom == ZoomType.ZOOM_IN_HORIZONTALLY || zoom == ZoomType.ZOOM_OUT_HORIZONTALLY) ) || (!isHorizontal() && (zoom == ZoomType.VERTICAL_ZOOM || zoom == ZoomType.ZOOM_OUT_VERTICALLY || zoom == ZoomType.ZOOM_IN_VERTICALLY) ); } /** * @param zoomType the zoomType to set */ public void setZoomType(final ZoomType zoomType) { this.zoomType = zoomType; // Set zoom's cursor if axis allows that type of zoom if (isValidZoomType(zoomType)) setCursor(zoomType.getCursor()); else setCursor(ZoomType.NONE.getCursor()); } /** * @return the titleFont */ public Font getTitleFont() { return titleFont; } /** * @return the autoScaleThreshold */ public double getAutoScaleThreshold() { return autoScaleThreshold; } /**Set this axis as Y-Axis or X-Axis. * @param isYAxis set true if the axis is Y-Axis; false if it is X-Axis. */ public void setYAxis(boolean isYAxis){ if(xyGraph != null) xyGraph.removeAxis(this); setOrientation(isYAxis ? Orientation.VERTICAL : Orientation.HORIZONTAL); if(xyGraph != null) xyGraph.addAxis(this); } /**Set the axis on primary side (Bottom/Left) or secondary side (Top/Right). * @param onPrimarySide set true if the axis on primary side(Bottom/Left); * false if it is not on the primary side of xy graph(Top/Right). */ public void setPrimarySide(boolean onPrimarySide){ setTickLableSide(onPrimarySide ? LabelSide.Primary : LabelSide.Secondary); } /** * @return true if the axis is Y-Axis; false if it is X-Axis; */ public boolean isYAxis(){ return !isHorizontal(); } /** * @return true if the axis is on the primary side of xy graph(Bottom/Left); * false if it is on the secondary side(Top/Right). */ public boolean isOnPrimarySide(){ return getTickLablesSide() == LabelSide.Primary; } /** Pan axis according to start/end from mouse listener */ private void pan() { if(isHorizontal()) pan(startRange, getPositionValue(start.x, false), getPositionValue(end.x, false)); else pan(startRange, getPositionValue(start.y, false), getPositionValue(end.y, false)); } /** Pan the axis * @param temp Original axis range before the panning started * @param t1 Start of the panning move * @param t2 End of the panning move */ void pan(final Range temp, double t1, double t2) { if (isLogScaleEnabled()) { final double m = Math.log10(t2) - Math.log10(t1); t1 = Math.pow(10,Math.log10(temp.getLower()) - m); t2 = Math.pow(10,Math.log10(temp.getUpper()) - m); } else { final double m = t2-t1; t1 = temp.getLower() - m; t2 = temp.getUpper() - m; } setRange(t1, t2); } /** Zoom axis * @param center Axis position at the 'center' of the zoom * @param factor Zoom factor. Positive to zoom 'in', negative 'out'. */ void zoomInOut(final double center, final double factor) { final double t1, t2; if (isLogScaleEnabled()) { final double l = Math.log10(getRange().getUpper()) - Math.log10(getRange().getLower()); final double r1 = (Math.log10(center) - Math.log10(getRange().getLower()))/l; final double r2 = (Math.log10(getRange().getUpper()) - Math.log10(center))/l; t1 = Math.pow(10, Math.log10(getRange().getLower()) + r1 * factor * l); t2 = Math.pow(10, Math.log10(getRange().getUpper()) - r2 * factor * l); }else{ final double l = getRange().getUpper() - getRange().getLower(); final double r1 = (center - getRange().getLower())/l; final double r2 = (getRange().getUpper() - center)/l; t1 = getRange().getLower() + r1 * factor * l; t2 = getRange().getUpper() - r2 * factor * l; } setRange(t1, t2, true); } /** * @param grid the grid to set */ public void setGrid(Grid grid) { this.grid = grid; } /** * @return the grid */ public Grid getGrid() { return grid; } @Override public void setLogScale(boolean enabled) throws IllegalStateException { // TODO Auto-generated method stub boolean old = isLogScaleEnabled(); super.setLogScale(enabled); fireAxisLogScaleChanged(old, logScaleEnabled); } private void fireAxisLogScaleChanged(boolean old, boolean logScale) { if(old == logScale) return; for(IAxisListener listener : listeners) listener.axisLogScaleChanged(this, old, logScale); } /** Listener to mouse events, performs panning and some zooms * Is very similar to the PlotMouseListener, but unclear * how easy/useful it would be to base them on the same code. */ class AxisMouseListener extends MouseMotionListener.Stub implements MouseListener { private SaveStateCommand command; public void mousePressed(final MouseEvent me) { // Only react to 'main' mouse button, only react to 'real' zoom if (me.button != 1 || !isValidZoomType(zoomType)) return; armed = true; // get start position switch (zoomType) { case RUBBERBAND_ZOOM: if(isHorizontal()) start = new Point(me.getLocation().x, bounds.y); else start = new Point(bounds.x, me.getLocation().y); end = null; break; case HORIZONTAL_ZOOM: start = new Point(me.getLocation().x, bounds.y); end = null; break; case VERTICAL_ZOOM: start = new Point(bounds.x, me.getLocation().y); end = null; break; case PANNING: setCursor(grabbing); start = me.getLocation(); end = null; startRange = getRange(); break; case ZOOM_IN: case ZOOM_IN_HORIZONTALLY: case ZOOM_IN_VERTICALLY: case ZOOM_OUT: case ZOOM_OUT_HORIZONTALLY: case ZOOM_OUT_VERTICALLY: start = me.getLocation(); end = new Point(); // Start timer that will zoom while mouse button is pressed Display.getCurrent().timerExec(ZOOM_SPEED, new Runnable() { public void run() { if (!armed) return; performInOutZoom(); Display.getCurrent().timerExec(ZOOM_SPEED, this); } }); break; default: break; } //add command for undo operation command = new AxisPanOrZoomCommand(zoomType.getDescription(), Axis.this); me.consume(); } public void mouseDoubleClicked(final MouseEvent me) { /* Ignored */ } @Override public void mouseDragged(final MouseEvent me) { if (! armed) return; switch (zoomType) { case RUBBERBAND_ZOOM: // Treat rubberband zoom on axis like horiz/vert. zoom if (isHorizontal()) end = new Point(me.getLocation().x, bounds.y + bounds.height); else end = new Point(bounds.x + bounds.width, me.getLocation().y); break; case HORIZONTAL_ZOOM: end = new Point(me.getLocation().x, bounds.y + bounds.height); break; case VERTICAL_ZOOM: end = new Point(bounds.x + bounds.width, me.getLocation().y); break; case PANNING: end = me.getLocation(); pan(); break; default: break; } Axis.this.repaint(); } @Override public void mouseExited(final MouseEvent me) { // Treat like releasing the button to stop zoomIn/Out timer switch (zoomType) { case ZOOM_IN: case ZOOM_IN_HORIZONTALLY: case ZOOM_IN_VERTICALLY: case ZOOM_OUT: case ZOOM_OUT_HORIZONTALLY: case ZOOM_OUT_VERTICALLY: mouseReleased(me); default: } } public void mouseReleased(final MouseEvent me) { if (! armed) return; armed = false; if (zoomType == ZoomType.PANNING) setCursor(zoomType.getCursor()); if (end == null || start == null || command == null) return; switch (zoomType) { case RUBBERBAND_ZOOM: case HORIZONTAL_ZOOM: case VERTICAL_ZOOM: performStartEndZoom(); break; case PANNING: pan(); break; case ZOOM_IN: case ZOOM_IN_HORIZONTALLY: case ZOOM_IN_VERTICALLY: case ZOOM_OUT: case ZOOM_OUT_HORIZONTALLY: case ZOOM_OUT_VERTICALLY: performInOutZoom(); break; default: break; } command.saveState(); xyGraph.getOperationsManager().addCommand(command); command = null; start = null; end = null; } /** Perform the zoom to mouse start/end */ private void performStartEndZoom() { final double t1 = getPositionValue(isHorizontal() ? start.x : start.y, false); final double t2 = getPositionValue(isHorizontal() ? end.x : end.y, false); setRange(t1, t2, true); } /** Perform the in or out zoom according to zoomType */ private void performInOutZoom() { final int pixel_pos = isHorizontal() ? start.x : start.y; final double center = getPositionValue(pixel_pos, false); switch (zoomType) { case ZOOM_IN: zoomInOut(center, ZOOM_RATIO); break; case ZOOM_IN_HORIZONTALLY: zoomInOut(center, ZOOM_RATIO); break; case ZOOM_IN_VERTICALLY: zoomInOut(center, ZOOM_RATIO); break; case ZOOM_OUT: zoomInOut(center, -ZOOM_RATIO); break; case ZOOM_OUT_HORIZONTALLY:zoomInOut(center, -ZOOM_RATIO); break; case ZOOM_OUT_VERTICALLY: zoomInOut(center, -ZOOM_RATIO); break; default: // NOP } } } }