ZoomPanPlot

ZoomPanPlot is the interactive plot base class used throughout Blackchirp. It subclasses both QwtPlot and SettingsStorage, combining Qwt’s rendering engine with persistent per-plot configuration (zoom factors, tracker options, grid style) stored under the plot’s unique name. Every plot widget in the application — FT view, FID view, tracking plots, LIF plots — inherits from ZoomPanPlot and obtains consistent zoom, pan, and curve-management behavior without reimplementing it.

Subclasses override the protected hook layer (pan, zoom, getLimitRect, buildContextMenu, contextMenu) to specialize behavior for their data type. The BlackchirpPlotCurveBase family provides the curve objects attached to the plot; curve creation is handled by CurveFactory.

Curve registry and thread safety

ZoomPanPlot runs an asynchronous worker (via QtConcurrent::run) that downsamples each attached curve’s data whenever the x range changes. Because that worker reads curve pointers, the set of attached curves cannot be mutated freely from the UI thread.

To make this safe, ZoomPanPlot keeps an internal registry (d_curveRegistry, guarded by p_mutex) of every BlackchirpPlotCurveBase attached to it, and exposes the only two supported attach/detach entry points:

  • attachCurve() — drains any in-flight filter pass, then attaches the curve and adds it to the registry.

  • detachCurve() — drains any in-flight filter pass, then detaches the curve and removes it from the registry.

When the worker is dispatched, the registry is snapshotted under p_mutex into a list of (curve*, x-axis canvasMap) tuples and the canvas width is captured. The lambda owns the snapshot by value, so the worker never touches the live registry, the QwtPlot item list, or any widget state. Bounding-rect recomputation runs in the QFutureWatcher::finished slot on the UI thread, again driven by the registry rather than itemList().

To make accidental misuse impossible to compile, QwtPlotItem::attach and QwtPlotItem::detach are made private inside BlackchirpPlotCurveBase via using-declarations, with ZoomPanPlot friended so its registry helpers can still call them. A direct curve->attach(plot) or curve->detach() call is therefore a hard compile error.

Curves held in std::unique_ptr (the common pattern for plot subclasses) do not require an explicit detachCurve before destruction: the ~BlackchirpPlotCurveBase destructor calls a private ZoomPanPlot::_unregisterCurve helper that drains the worker and removes the pointer from the registry before ~QwtPlotItem performs its own detach. This makes d_overlayCurves.clear(), d_overlayCurves.erase(it), and unique_ptr::reset() all safe with respect to the worker.

Items that are not BlackchirpPlotCurveBase (markers, labels, grids, spectrograms) do not participate in the filter pass and should be attached and detached via the standard QwtPlotItem API.

resetPlot() clears the registry, then calls detachItems(QwtPlotItem::Rtti_PlotItem, false) so subclasses that own their items via std::unique_ptr (or any other external owner) are safe to call it without risking a double free.

Modifier-key contract

Mouse-wheel zoom

Mouse-wheel events on the plot canvas zoom all applicable axes simultaneously. Holding a modifier key locks one or more axes:

Modifier

Effect

(none)

Zoom all axes around the mouse pointer.

Ctrl

Lock both y axes; zoom x axes only.

Shift

Lock both x axes; zoom y axes only.

Meta

Lock x axes (xBottom, xTop) and yRight; zoom yLeft only.

Alt

Lock x axes (xBottom, xTop) and yLeft; zoom yRight only (Qt may remap Alt+wheel to a horizontal scroll event; the implementation falls back to the x-angle delta in that case).

Each wheel notch zooms by the per-axis zoomFactor fraction (default 10 %). The zoom is anchored at the mouse-pointer position, so the data point under the cursor stays fixed.

Keyboard navigation

Keyboard events are processed when the plot canvas has focus:

Key

Effect

Home

Autoscale all axes.

Left / Right

Pan x axes by 50 % of the current range.

Up / Down

Pan y axes by 50 % of the current range.

Ctrl + Left / Right

Zoom out / in x axes by a factor of 1.5.

Ctrl + Up / Down

Zoom in / out y axes by a factor of 1.5.

Shift + Left / Right

Zoom out / in x axes by a factor of 1.1.

Shift + Up / Down

Zoom in / out y axes by a factor of 1.1.

Alt + Left / Right

Pan x axes by 10 % of the current range.

Alt + Up / Down

Pan y axes by 10 % of the current range.

While Ctrl is held, the rubber-band zoomer locks the y axis (zoom-box changes x only). While Shift is held, it locks the x axis.

Mouse gestures

Gesture

Effect

Left-button drag

Rubber-band zoom (via CustomZoomer).

Middle-button drag

Drag-pan all non-override axes.

Double-click (any button)

Autoscale all axes.

Right-click

Context menu (see contextMenu()).

Autoscale model

Each of the four Qwt axes (xBottom, xTop, yLeft, yRight) has an independent AxisConfig record. Per-axis state is held in AxisConfig; see the rendered struct below for individual flag semantics.

Settings keys

The following keys are stored in SettingsStorage under the plot’s name. Per-axis values live in a sub-array keyed by BC::Key::axes; each element of the array corresponds to one axis in the order yLeft, yRight, xBottom, xTop.

Key

Type

Description

BC::Key::axes (array)

array

Per-axis sub-maps; each entry holds zoomFactor, trackerDecimals, and trackerScientific.

BC::Key::zoomFactor

double

Fractional zoom step per wheel notch for one axis (default 0.1).

BC::Key::trackerDecimals

int

Decimal places shown in the cursor tracker for one axis (default 4).

BC::Key::trackerScientific

bool

Cursor tracker uses scientific notation for one axis (default false).

BC::Key::trackerEn

bool

Whether the cursor-position tracker overlay is shown (default false).

BC::Key::kzCenter

bool

Keyboard Y-zoom anchors to canvas center when true (default false).

BC::Key::majorGridColor

QColor

Color of the major grid lines.

BC::Key::majorGridStyle

Qt::PenStyle

Dash pattern of the major grid lines (default Qt::NoPen).

BC::Key::minorGridColor

QColor

Color of the minor grid lines.

BC::Key::minorGridStyle

Qt::PenStyle

Dash pattern of the minor grid lines (default Qt::NoPen).

Curve-management slot family

ZoomPanPlot provides a set of slots that apply an appearance change to a BlackchirpPlotCurveBase and then emit curveMetadataChanged() and trigger a replot. These slots are called by the per-curve appearance widget embedded in the context menu and may also be called directly by subclasses.

The setCurveColor() slot opens a QColorDialog; all other slots in the family accept a value directly:

setCurveColor, setCurveStyle, setCurveLineThickness, setCurveLineStyle, setCurveMarker, setCurveMarkerSize, setCurveVisible, setCurveAutoscale, setCurveAxisY.

The context menu also contains an Export XY action for each curve that calls exportCurve(), opening a save-file dialog and writing the curve data as a CSV file.

API Reference

class ZoomPanPlot : public QwtPlot, public SettingsStorage

QwtPlot subclass providing interactive zoom, pan, and curve management.

ZoomPanPlot wraps QwtPlot with mouse and keyboard navigation, autoscaling, per-axis configuration, and a curve-appearance context menu. It also inherits SettingsStorage so all interactive preferences (zoom factors, tracker options, grid style) survive across sessions. Subclasses override the protected hook layer to add data filtering, custom context-menu entries, and axis-limit policies appropriate for their data type.

Subclassed by ChirpConfigPlot, FidPlot, FtPlot, LifSlicePlot, LifSpectrogramPlot, LifTracePlot, PulsePlot, TrackingPlot

Public Functions

explicit ZoomPanPlot(const QString &name, QWidget *parent = nullptr)

Constructs a ZoomPanPlot with the given settings storage name.

Parameters:
  • name – Unique name used as the SettingsStorage key; appears in context menus and exported file names.

  • parent – Optional Qt parent widget.

virtual ~ZoomPanPlot()

Destructor; waits for any in-flight data-filter operation to finish.

bool isAutoScale()

Returns true when every axis has autoscale enabled.

void resetPlot()

Detaches all plot items and resets all axes to autoscale.

Items are detached without being deleted (autoDelete=false), so subclasses that own their items via std::unique_ptr (or any other external owner) are safe to call this. Curves are also removed from the internal registry so the next filter pass sees an empty set.

void setSpectrogramMode(bool b = true)

Switches the plot into spectrogram mode.

In spectrogram mode the yRight and xTop axes are excluded from mouse-wheel zoom and drag-pan interactions so the color-axis range is managed separately.

Parameters:

btrue to enable spectrogram mode (default), false to disable.

void setXRanges(const QwtScaleDiv &bottom, const QwtScaleDiv &top)

Sets the x-axis scale divisions directly and disables autoscale on both x axes.

Parameters:
  • bottom – Scale division for the xBottom axis.

  • top – Scale division for the xTop axis.

inline void setMaxIndex(int i)

Sets the maximum plot-index value used by the “Change plot” context-menu action.

Parameters:

i – Maximum zero-based plot index; set to 0 to hide the action.

void setPlotTitle(const QString &text)

Sets the plot title text.

Parameters:

text – Title string.

void setPlotAxisTitle(QwtPlot::Axis a, const QString &text)

Sets the title text for a single axis.

Parameters:
  • a – Axis identifier (e.g. QwtPlot::xBottom).

  • text – Title string.

void attachCurve(BlackchirpPlotCurveBase *curve)

Attaches curve to this plot and registers it for the filter pass.

This is the only supported way to attach a BlackchirpPlotCurveBase to a ZoomPanPlot. QwtPlotItem::attach() is hidden in BlackchirpPlotCurveBase to prevent direct attach/detach calls that would race with the asynchronous filter pass driven by replot().

Blocks until any in-flight filter pass completes, then attaches the curve and adds it to the internal registry under p_mutex. Safe to call multiple times with the same curve (subsequent calls are no-ops after the registry insert is deduplicated).

Parameters:

curve – Curve to attach; must not be null.

void detachCurve(BlackchirpPlotCurveBase *curve)

Detaches curve from this plot and removes it from the registry.

Blocks until any in-flight filter pass completes before detaching, so the worker cannot dereference the curve after it is removed. Safe to call on a curve that is not currently attached.

Parameters:

curve – Curve to detach; must not be null.

virtual QSize sizeHint() const
virtual QSize minimumSizeHint() const

Public Members

const QString d_name

Unique plot name used as the SettingsStorage key.

Public Slots

void autoScale()

Enables autoscale on all axes and triggers a replot.

void overrideAxisAutoScaleRange(QwtPlot::Axis a, double min, double max)

Pins the autoscale range of an axis to [min, max] without disabling autoscale.

When overrideAutoScaleRange is set, autoscale still operates but cannot expand beyond the specified range. This is useful for axes whose data range is constrained by the experiment configuration.

Parameters:
  • a – Axis to constrain.

  • min – Lower bound of the override range.

  • max – Upper bound of the override range.

void clearAxisAutoScaleOverride(QwtPlot::Axis a)

Clears an axis autoscale-range override, allowing the data bounding rect to drive scaling.

Parameters:

a – Axis whose override should be removed.

virtual void replot()

Recomputes axis scales and triggers a Qwt replot; starts an async data-filter pass when the x range changes.

void setZoomFactor(QwtPlot::Axis a, double v)

Stores a new wheel-zoom step factor for the given axis.

Parameters:
  • a – Axis identifier.

  • v – Fractional zoom step per wheel notch (e.g. 0.1 = 10 % per step).

void setKeyZoomYCenter(bool en)

Controls whether keyboard Y-zoom is anchored to the plot center.

When disabled (default), vertical keyboard zoom is symmetric about y = 0. When enabled, it is symmetric about the current plot-canvas center.

Parameters:

entrue to anchor at center.

void setTrackerEnabled(bool en)

Shows or hides the cursor-position tracker overlay.

Parameters:

entrue to enable the tracker.

void setTrackerDecimals(QwtPlot::Axis a, int dec)

Sets the number of decimal places shown by the cursor tracker for one axis.

Parameters:
  • a – Axis identifier.

  • dec – Number of decimal places (0–9).

void setTrackerScientific(QwtPlot::Axis a, bool sci)

Enables or disables scientific notation in the cursor tracker for one axis.

Parameters:
  • a – Axis identifier.

  • scitrue to use scientific notation.

void exportCurve(BlackchirpPlotCurveBase *curve)

Opens a save-file dialog and exports the curve’s XY data as CSV.

Parameters:

curve – Curve whose data is exported; the curve’s name is used as the default filename.

void setCurveColor(BlackchirpPlotCurveBase *curve)

Opens a color-picker dialog and applies the chosen color to curve.

Emits curveMetadataChanged() and triggers a replot on success.

Parameters:

curve – Curve to recolor.

void setCurveStyle(BlackchirpPlotCurveBase *curve, QwtPlotCurve::CurveStyle s)

Sets the Qwt curve drawing style for curve.

Parameters:
  • curve – Target curve.

  • s – New curve style (e.g. QwtPlotCurve::Lines).

void setCurveLineThickness(BlackchirpPlotCurveBase *curve, double t)

Sets the pen line thickness for curve.

Parameters:
  • curve – Target curve.

  • t – Line width in pixels.

void setCurveLineStyle(BlackchirpPlotCurveBase *curve, Qt::PenStyle s)

Sets the pen dash pattern for curve.

Parameters:
  • curve – Target curve.

  • s – Qt::PenStyle dash pattern.

void setCurveMarker(BlackchirpPlotCurveBase *curve, QwtSymbol::Style s)

Sets the point-marker symbol style for curve.

Parameters:
  • curve – Target curve.

  • s – QwtSymbol::Style marker type.

void setCurveMarkerSize(BlackchirpPlotCurveBase *curve, int s)

Sets the pixel size of the point-marker symbol for curve.

Parameters:
  • curve – Target curve.

  • s – Marker diameter in pixels.

void setCurveVisible(BlackchirpPlotCurveBase *curve, bool v)

Sets visibility for curve, persisting the choice to storage.

Parameters:
  • curve – Target curve.

  • vtrue to show the curve.

void setCurveAutoscale(BlackchirpPlotCurveBase *curve, bool enabled)

Enables or disables autoscale participation for curve.

A curve with autoscale disabled is still drawn but its bounding rect does not influence axis range calculations.

Parameters:
  • curve – Target curve.

  • enabledtrue to include in autoscale.

void setCurveAxisY(BlackchirpPlotCurveBase *curve, QwtPlot::Axis a)

Moves curve to a different y axis.

Parameters:
  • curve – Target curve.

  • a – Destination y axis (QwtPlot::yLeft or QwtPlot::yRight).

void zoomToPeak(double xCenter, double xHalfWidth, double intensity)

Frames the plot on a peak: a fixed x window and an explicit y window.

The x axis is set to [xCenter - xHalfWidth, xCenter + xHalfWidth]. The y axis is framed explicitly (not autoscaled) so the peak is clearly visible: [0, 1.25·|I|] when the current yLeft range is non-negative, or the symmetric [-1.25·|I|, +1.25·|I|] when it currently dips below zero. Both axes leave autoscale disabled, so the context-menu Autoscale action restores the data-driven view.

Parameters:
  • xCenter – Peak center (x data units).

  • xHalfWidth – Half-width of the x window (x data units).

  • intensity – Peak intensity, used to size the y window.

void setYRangeOverride(double min, double max)

Sets an explicit yLeft window and disables yLeft autoscale.

Symmetric with setXRanges(): the range holds until the next autoScale(). Keeps d_config manipulation inside the class so callers never poke axis state directly.

Parameters:
  • min – Lower yLeft bound.

  • max – Upper yLeft bound.

void configureGridMajorPen()

Reads the major-grid color and style from settings and applies them to the grid pen.

void configureGridMinorPen()

Reads the minor-grid color and style from settings and applies them to the grid pen.

Signals

void panningStarted()

Emitted when the user begins dragging with the middle mouse button.

void panningFinished()

Emitted when the user releases the middle mouse button after panning.

void plotRightClicked(QMouseEvent *ev)

Emitted on a right-click on the plot canvas before the context menu appears.

Parameters:

ev – The originating mouse event.

void curveMoveRequested(BlackchirpPlotCurve *curve, int index)

Emitted when the user selects “Change plot” for a BlackchirpPlotCurve.

Parameters:
  • curve – Curve to move.

  • index – Zero-based target plot index.

void curveMetadataChanged(BlackchirpPlotCurveBase *curve)

Emitted after any slot in the curve-management family modifies a curve’s appearance or settings.

Parameters:

curve – The modified curve.

void visibleXRangeChanged(double min, double max)

Emitted once per settled xBottom range after pan/zoom/autoscale.

Debounced: replot() fires on every mouse-move during a pan and splits across a synchronous/asynchronous pair, so this is driven by a short single-shot timer that samples the finalized xBottom interval once activity quiesces. Suppressed when the range has not moved since the previous emit.

Parameters:
  • min – Lower xBottom bound (data units).

  • max – Upper xBottom bound (data units).

Protected Functions

void setAxisOverride(QwtPlot::Axis axis, bool override = true)

Locks the named axis into a fixed visible state, overriding automatic show/hide.

Parameters:
  • axis – Axis to lock.

  • overridetrue to lock (default), false to restore automatic control.

void waitForFilterComplete()

Blocks until any concurrent data-filter operation completes.

virtual void resizeEvent(QResizeEvent *ev)

Handles canvas resize events; updates axis fonts and marks x as dirty.

virtual bool eventFilter(QObject *obj, QEvent *ev)

Routes canvas events to the zoom, pan, and keyboard handlers.

Subclasses that override this method should call the base implementation for events they do not consume, or navigation behavior will be lost.

virtual void pan(QMouseEvent *me)

Translates the axis scales based on the delta from the last pan position.

Parameters:

me – Mouse move event carrying the current cursor position.

virtual void panH(double factor)

Pans both x axes by a fraction of their current range.

Parameters:

factor – Signed fraction of the current range to shift (positive = right/up, negative = left/down).

virtual void panV(double factor)

Pans both y axes by a fraction of their current range.

Parameters:

factor – Signed fraction of the current range to shift.

virtual void zoom(QWheelEvent *we)

Zooms all applicable axes in response to a wheel event.

The modifier-key contract for wheel zoom is:

  • No modifier — zoom all axes around the mouse pointer.

  • Ctrl — lock both y axes (zoom x only).

  • Shift — lock horizontal axes (zoom y only, symmetric about center).

  • Meta — lock yRight (zoom xBottom, xTop, and yLeft).

  • Alt — lock both y axes and horizontal axes simultaneously (effectively locks all; useful when Qt remaps Alt+wheel to x scroll).

Each wheel notch zooms by the per-axis zoomFactor fraction.

Parameters:

we – Wheel event from the canvas.

virtual void zoom(const QRectF &rect, Axis xAx, Axis yAx)

Zooms to a rectangle selected by the rubber-band zoomer.

Parameters:
  • rect – Target rectangle in plot coordinates.

  • xAx – X axis of the zoomer pair.

  • yAx – Y axis of the zoomer pair.

virtual void zoom(Axis ax, double factor)

Zooms a single axis by a scale factor around the current center.

For y axes, when keyZoomYCenter is false the zoom is symmetric about y = 0 (the default, suited to FID/FT plots); when true it is symmetric about the current canvas center.

Parameters:
  • ax – Axis to zoom.

  • factor – Values > 1 zoom out; values < 1 zoom in.

virtual QRectF getLimitRect(Axis xAx, Axis yAx) const

Returns the maximum allowed axis extent for a pair of axes.

The limit rect is the union of the overrideRect (if active) or the data bounding rect for each of the two axes. Zoom and pan operations use this to prevent the view from moving outside the data range.

Parameters:
  • xAx – X axis of the pair.

  • yAx – Y axis of the pair.

virtual void buildContextMenu(QMouseEvent *me)

Creates and displays the context menu at the position of me.

The default implementation delegates to contextMenu() and calls QMenu::popup(). Subclasses that want to intercept right-click without replacing the menu may connect to plotRightClicked() instead.

Parameters:

me – Right-click mouse event.

virtual QMenu *contextMenu()

Constructs and returns the right-click context menu.

The returned menu includes: Autoscale, Zoom Settings (per-axis zoom factors and keyboard Y-center toggle), Tracker (enable/disable, decimals, scientific notation), Grid (major/minor pen color and style), and Curves (per-curve appearance widget, Export XY, and Change Plot). Subclasses may override to extend or replace the menu; the returned QMenu is owned by the caller (has Qt::WA_DeleteOnClose set).

QMenu *buildCurveAppearanceMenu(BlackchirpPlotCurveBase *curve, QWidget *parent)

Builds a menu containing the full curve-appearance editor for curve.

The returned QMenu holds a single QWidgetAction wrapping a CurveAppearanceWidget wired to the live curve-update slots (color, style, thickness, marker, visibility, autoscale, y axis) and to the appearance-preset save/delete dialogs. It is the shared builder used both by the per-curve right-click submenu and by discoverability shortcuts that pop the editor elsewhere (e.g. a toolbar button). The caller owns the returned menu and decides its lifetime (parent ownership for an embedded submenu, or Qt::WA_DeleteOnClose for a standalone popup).

Parameters:
  • curve – Curve whose appearance the widget edits; must not be null.

  • parent – Parent object for the returned menu.

virtual void showEvent(QShowEvent *event)

Protected Attributes

int d_maxIndex

Maximum valid plot index; drives the “Change plot” submenu entry count.

QwtPlotGrid *p_grid

Grid item attached to the plot.

CustomTracker *p_tracker

Cursor-position tracker overlay.

CustomZoomer *p_zoomerLB

Rubber-band zoomer for the xBottom/yLeft axis pair.

CustomZoomer *p_zoomerRT

Rubber-band zoomer for the xTop/yRight axis pair.

Private Functions

void _unregisterCurve(BlackchirpPlotCurveBase *curve)

Removes curve from the registry; called by ~BlackchirpPlotCurveBase.

Drains any in-flight filter pass first so the worker cannot hold a stale snapshot pointer to a curve that is mid-destruction.

Parameters:

curve – Curve being destroyed; may or may not be in the registry.

void _kickoffFilterPass()

Snapshots the registry and launches the asynchronous filter worker.

Builds a list of (curve, x-axis canvasMap) pairs under p_mutex, captures the canvas width, and dispatches the worker via QtConcurrent::run. The lambda owns its snapshot by value, so the worker never touches the live curve registry, the QwtPlot item list, or any widget state. Sets d_busy = true.

void _recomputeBoundingRects()

Recomputes per-axis bounding rects from the current registry.

Runs on the UI thread (called from the QFutureWatcher::finished slot). Mirrors the bounding-rect union logic in replot(), but limited to the curve registry — markers and other items are handled by replot(). Caller must hold p_mutex.

void _emitVisibleXRange()

Samples the finalized xBottom interval and emits visibleXRangeChanged if it moved.

Private Members

PlotConfig d_config
QFutureWatcher<void> *p_watcher
bool d_busy = {false}
QMutex *p_mutex
QList<BlackchirpPlotCurveBase*> d_curveRegistry

Curves the worker is allowed to filter; guarded by p_mutex.

QTimer *p_xRangeDebounce

Single-shot timer coalescing visibleXRangeChanged emits.

double d_lastEmitXMin

xBottom lower bound at the last visibleXRangeChanged emit.

double d_lastEmitXMax

xBottom upper bound at the last visibleXRangeChanged emit.

Friends

friend class BlackchirpPlotCurveBase
struct AxisConfig

Per-axis persistent configuration.

One AxisConfig is held for each of the four QwtPlot axes. The autoScale flag drives whether the axis follows the data bounding rect on each replot. override suppresses the automatic enable/disable of an axis based on whether any items are attached to it. overrideAutoScaleRange, when true, constrains autoscaling to the rectangle stored in overrideRect rather than the data rect.

Public Functions

inline explicit AxisConfig(int i, const QString n)
inline AxisConfig()

Public Members

int index = {-1}

Position index into the BC::Key::axes settings array.

bool autoScale = {true}

When true, the axis range tracks attached item bounding rects.

bool override = {false}

When true, the axis visible state is not controlled automatically.

bool overrideAutoScaleRange = {false}

When true, autoscale is clamped to overrideRect.

QRectF overrideRect = {1.0, 1.0, -2.0, -2.0}

Range constraint applied when overrideAutoScaleRange is true.

QRectF boundingRect = {1.0, 1.0, -2.0, -2.0}

Union of all attached items’ bounding rects (updated each replot).

double zoomFactor = {0.1}

Fractional zoom step per mouse-wheel notch.

QString name

Human-readable axis name used in context-menu labels.

struct PlotConfig

Top-level plot interaction state.

Public Members

std::map<Axis, AxisConfig> axisMap

Per-axis configuration keyed by QwtPlot::Axis.

bool xDirty = {false}

True when the x range changed and a new data-filter pass is needed.

bool panning = {false}

True while the user is drag-panning.

bool spectrogramMode = {false}

When true, yRight and xTop are excluded from zoom and pan.

QPoint panClickPos

Canvas position where the current pan gesture started.

bool keyZoomYCenter = {false}

When true, keyboard Y-zoom centers on the canvas midpoint.