diff --git a/fsl/fslview/orthopanel.py b/fsl/fslview/orthopanel.py
index f5475bacfb47c4d9aee8f45c9b47c39cd45df4bb..c6ca3c2e07580ac81c4274cb176fcac02002a1f6 100644
--- a/fsl/fslview/orthopanel.py
+++ b/fsl/fslview/orthopanel.py
@@ -8,24 +8,36 @@
 #
 
 import wx
-import wx.lib.newevent as wxevent
 
 import fsl.props               as props
 import fsl.data.fslimage       as fslimage
 import fsl.fslview.slicecanvas as slicecanvas
 
-# The OrthoPanel emits a LocationEvent whenever the 'cursor' location
-# changes. It contains three attributes, x, y, and z, corresponding to
-# the current cursor location in the image space.
-LocationEvent, EVT_LOCATION_EVENT = wxevent.NewEvent()
-
 
 class OrthoPanel(wx.Panel, props.HasProperties):
 
-    displayXCanvas = props.Boolean(default=True)
-    displayYCanvas = props.Boolean(default=True)
-    displayZCanvas = props.Boolean(default=True)
-
+    showXCanvas = props.Boolean(default=True)
+    showYCanvas = props.Boolean(default=True)
+    showZCanvas = props.Boolean(default=True)
+    showCursor  = props.Boolean(default=True)
+
+    xpos  = props.Double(clamped=True)
+    ypos  = props.Double(clamped=True)
+    zpos  = props.Double(clamped=True)
+
+    xCanvasZoom = props.Double(minval=1.0,
+                               maxval=20.0, 
+                               default=1.0,
+                               clamped=True)
+    yCanvasZoom = props.Double(minval=1.0,
+                               maxval=20.0, 
+                               default=1.0,
+                               clamped=True)
+    zCanvasZoom = props.Double(minval=1.0,
+                               maxval=20.0, 
+                               default=1.0,
+                               clamped=True)
+        
     def __init__(self, parent, imageList):
         """
         Creates three SliceCanvas objects, each displaying the images
@@ -37,6 +49,7 @@ class OrthoPanel(wx.Panel, props.HasProperties):
                 'imageList must be a fsl.data.fslimage.ImageList instance')
 
         self.imageList = imageList
+        self.name      = 'OrthoPanel_{}'.format(id(self))
 
         wx.Panel.__init__(self, parent)
         self.SetMinSize((300, 100))
@@ -63,38 +76,136 @@ class OrthoPanel(wx.Panel, props.HasProperties):
         self.ycanvas.Bind(wx.EVT_MOTION,    self._setCanvasPosition)
         self.zcanvas.Bind(wx.EVT_MOTION,    self._setCanvasPosition)
 
+        xmin = imageList.minBounds[0]
+        xmax = imageList.maxBounds[0]
+        ymin = imageList.minBounds[1]
+        ymax = imageList.maxBounds[1] 
+        zmin = imageList.minBounds[2]
+        zmax = imageList.maxBounds[2]
+
+        self.xpos = xmin + abs(xmax - xmin) / 2.0
+        self.ypos = ymin + abs(ymax - ymin) / 2.0
+        self.zpos = zmin + abs(zmax - zmin) / 2.0
+        
+        self.imageList.addListener(lambda il: self._updateImageBounds())
+        self._updateImageBounds()
+
+        self._configShowListeners()
+        self._configZoomListeners()
+
+
+    def _configShowListeners(self):
+        def showCursor(ctx, value, valid):
+            self.xcanvas.showCursor = value
+            self.ycanvas.showCursor = value
+            self.zcanvas.showCursor = value
+
+        def showXCanvas(ctx, value, valid):
+            self.sizer.Show(self.xcanvas, value)
+            self.Layout()
+        def showYCanvas(ctx, value, valid):
+            self.sizer.Show(self.ycanvas, value)
+            self.Layout()
+        def showZCanvas(ctx, value, valid):
+            self.sizer.Show(self.zcanvas, value)
+            self.Layout() 
+
+        self.addListener('showCursor',  self.name, showCursor)
+        self.addListener('showXCanvas', self.name, showXCanvas)
+        self.addListener('showYCanvas', self.name, showYCanvas)
+        self.addListener('showZCanvas', self.name, showZCanvas)
+
+            
+    def _configZoomListeners(self):
+
+
+        def xzoom(ctx, value, valid):
+            value = 1.0 / value
+            xlen = value * abs(self.imageList.maxBounds[1] -
+                               self.imageList.minBounds[1])
+            ylen = value * abs(self.imageList.maxBounds[2] -
+                               self.imageList.minBounds[2])
+
+            if value == 1:
+                xcentre = self.imageList.minBounds[1] + 0.5 * xlen
+                ycentre = self.imageList.minBounds[2] + 0.5 * ylen
+            else:
+                xcentre = self.ypos
+                ycentre = self.zpos
+
+            self.xcanvas.xmin = xcentre - 0.5 * xlen
+            self.xcanvas.xmax = xcentre + 0.5 * xlen
+            self.xcanvas.ymin = ycentre - 0.5 * ylen
+            self.xcanvas.ymax = ycentre + 0.5 * ylen 
+
+        self.addListener('xCanvasZoom', self.name, xzoom)
+
         
-    def setLocation(self, x, y, z):
+    def _updateImageBounds(self):
         """
-        Programmatically set the currently displayed location
-        on each of the canvases. This does not trigger an
-        EVT_LOCATION_EVENT.
         """
-
-        self.xcanvas.xpos = y
-        self.xcanvas.ypos = z
-        self.xcanvas.zpos = x
-
-        self.ycanvas.xpos = x
-        self.ycanvas.ypos = z
-        self.ycanvas.zpos = y
         
-        self.zcanvas.xpos = x
-        self.zcanvas.ypos = y
-        self.zcanvas.zpos = z
-
-        self.xcanvas.Refresh()
-        self.ycanvas.Refresh()
-        self.zcanvas.Refresh()
-
-    def setXLocation(self, x):
-        self.setLocation(x, self.ycanvas.zpos, self.zcanvas.zpos)
-
-    def setYLocation(self, y):
-        self.setLocation(self.xcanvas.zpos, y, self.zcanvas.zpos)
+        xmin = self.imageList.minBounds[0]
+        xmax = self.imageList.maxBounds[0]
+        ymin = self.imageList.minBounds[1]
+        ymax = self.imageList.maxBounds[1]
+        zmin = self.imageList.minBounds[2]
+        zmax = self.imageList.maxBounds[2]
+
+        self.setConstraint('xpos', 'minval', xmin)
+        self.setConstraint('xpos', 'maxval', xmax)
+        self.setConstraint('ypos', 'minval', ymin)
+        self.setConstraint('ypos', 'maxval', ymax)
+        self.setConstraint('zpos', 'minval', zmin)
+        self.setConstraint('zpos', 'maxval', zmax)
+        
+        # reset the cursor and min/max values in
+        # case the old values were out of bounds
+        self.xpos = self.xpos
+        self.ypos = self.ypos
+        self.zpos = self.zpos
+
+
+    def _shiftCanvas(self, canvas, xax, yax, newx, newy):
+
+        if newx >= canvas.xmin and \
+           newx <= canvas.xmax and \
+           newy >= canvas.ymin and \
+           newy <= canvas.ymax:
+            return
+
+        xshift = 0
+        yshift = 0
+
+        imgxmin = self.imageList.minBounds[xax]
+        imgxmax = self.imageList.maxBounds[xax]
+        imgymin = self.imageList.minBounds[yax]
+        imgymax = self.imageList.maxBounds[yax] 
+
+        if   newx < canvas.xmin: xshift = newx - canvas.xmin
+        elif newx > canvas.xmax: xshift = newx - canvas.xmax
+        if   newy < canvas.ymin: yshift = newy - canvas.ymin 
+        elif newy > canvas.ymax: yshift = newy - canvas.ymax 
+            
+        newxmin = canvas.xmin + xshift 
+        newxmax = canvas.xmax + xshift
+        newymin = canvas.ymin + xshift 
+        newymax = canvas.ymax + xshift 
+
+        if   newxmin < imgxmin: xshift = newxmin - imgxmin 
+        elif newxmax > imgxmax: xshift = newxmax - imgxmax
+        
+        if   newymin < imgymin: yshift = newymin - imgymin
+        elif newymax > imgymax: yshift = newymax - imgymax 
 
-    def setZLocation(self, z):
-        self.setLocation(self.xcanvas.zpos, self.ycanvas.zpos, z)
+        if xshift != 0:
+            print 'xshift {}'.format(xshift)
+            canvas.xmin = canvas.xmin + xshift
+            canvas.xmax = canvas.xmax + xshift
+        if yshift != 0:
+            print 'yshift {}'.format(yshift)
+            canvas.ymin = canvas.ymin + yshift
+            canvas.ymax = canvas.ymax + yshift
 
 
     def _setCanvasPosition(self, ev):
@@ -115,23 +226,67 @@ class OrthoPanel(wx.Panel, props.HasProperties):
 
         my = h - my
 
-        x = self.xcanvas.zpos
-        y = self.ycanvas.zpos
-        z = self.zcanvas.zpos
-
-        mx = source.canvasToWorldX(mx)
-        my = source.canvasToWorldY(my)
-
-        if   source == self.xcanvas: y, z = mx, my
-        elif source == self.ycanvas: x, z = mx, my
-        elif source == self.zcanvas: x, y = mx, my
-
-        self.setLocation(x, y, z)
-
-        evt = LocationEvent(x=x, y=y, z=z)
-        wx.PostEvent(self, evt)
-
-
+        xpos = source.canvasToWorldX(mx)
+        ypos = source.canvasToWorldY(my)
+
+        if source == self.xcanvas:
+            self.ypos         = xpos
+            self.zpos         = ypos
+            
+            self.xcanvas.xpos = xpos
+            self.xcanvas.ypos = ypos
+            
+            self.ycanvas.ypos = ypos
+            self.ycanvas.zpos = xpos
+
+            self.zcanvas.ypos = xpos
+            self.zcanvas.zpos = ypos
+
+            if self.yCanvasZoom != 1:
+                self._shiftCanvas(self.ycanvas, 0, 2, self.ycanvas.xpos, ypos)
+
+            if self.zCanvasZoom != 1:
+                self._shiftCanvas(self.zcanvas, 0, 1, self.zcanvas.xpos, xpos)
+                                 
+        elif source == self.ycanvas:
+            self.xpos         = xpos
+            self.zpos         = ypos
+            
+            self.ycanvas.xpos = xpos
+            self.ycanvas.ypos = ypos
+
+            self.xcanvas.ypos = ypos
+            self.xcanvas.zpos = xpos
+
+            self.zcanvas.xpos = xpos
+            self.zcanvas.zpos = ypos
+
+            if self.xCanvasZoom != 1:
+                self._shiftCanvas(self.xcanvas, 1, 2, self.xcanvas.xpos, ypos)
+
+            if self.zCanvasZoom != 1:
+                self._shiftCanvas(self.zcanvas, 0, 1, xpos, self.zcanvas.ypos)
+            
+        elif source == self.zcanvas:
+            self.xpos         = xpos
+            self.ypos         = ypos
+            
+            self.zcanvas.xpos = xpos
+            self.zcanvas.ypos = ypos
+
+            self.xcanvas.xpos = ypos
+            self.xcanvas.zpos = xpos
+
+            self.ycanvas.xpos = xpos
+            self.ycanvas.zpos = ypos
+
+            if self.xCanvasZoom != 1:
+                self._shiftCanvas(self.xcanvas, 1, 2, ypos, self.xcanvas.ypos)
+
+            if self.yCanvasZoom != 1:
+                self._shiftCanvas(self.ycanvas, 0, 2, xpos, self.ycanvas.ypos)
+
+            
 class OrthoFrame(wx.Frame):
     """
     Convenience class for displaying an OrthoPanel in a standalone window.
diff --git a/fsl/fslview/slicecanvas.py b/fsl/fslview/slicecanvas.py
index 53476af868c199cd084b918cb7924111ac6ff9b9..4069541fa1617652d2f696c539a81b8b58105bbd 100644
--- a/fsl/fslview/slicecanvas.py
+++ b/fsl/fslview/slicecanvas.py
@@ -158,16 +158,19 @@ class SliceCanvas(wxgl.GLCanvas, props.HasProperties):
 
         # when any of the xyz properties of
         # this canvas change, we need to redraw
-        self.addListener('xpos',       self.name, self._refresh)
-        self.addListener('ypos',       self.name, self._refresh)
-        self.addListener('zpos',       self.name, self._refresh)
-        self.addListener('xmin',       self.name, self._refresh)
-        self.addListener('xmax',       self.name, self._refresh)
-        self.addListener('ymin',       self.name, self._refresh)
-        self.addListener('ymax',       self.name, self._refresh)
-        self.addListener('zmin',       self.name, self._refresh)
-        self.addListener('zmax',       self.name, self._refresh)
-        self.addListener('showCursor', self.name, self._refresh) 
+        def posRefresh(  *a): self._refresh()
+        def boundRefresh(*a): self._refresh(True)
+            
+        self.addListener('xpos',       self.name, posRefresh)
+        self.addListener('ypos',       self.name, posRefresh)
+        self.addListener('zpos',       self.name, posRefresh)
+        self.addListener('xmin',       self.name, boundRefresh)
+        self.addListener('xmax',       self.name, boundRefresh)
+        self.addListener('ymin',       self.name, boundRefresh)
+        self.addListener('ymax',       self.name, boundRefresh)
+        self.addListener('zmin',       self.name, boundRefresh)
+        self.addListener('zmax',       self.name, boundRefresh)
+        self.addListener('showCursor', self.name, boundRefresh) 
 
         # When drawn, the slice does not necessarily take
         # up the entire canvas size, as its aspect ratio
@@ -272,12 +275,14 @@ class SliceCanvas(wxgl.GLCanvas, props.HasProperties):
             glData = glimagedata.GLImageData(image, self.xax, self.yax)
             image.setAttribute(self.name, glData)
 
-            image.display.addListener('enabled',    self.name, self._refresh)
-            image.display.addListener('alpha',      self.name, self._refresh)
-            image.display.addListener('displayMin', self.name, self._refresh)
-            image.display.addListener('displayMax', self.name, self._refresh)
-            image.display.addListener('rangeClip',  self.name, self._refresh)
-            image.display.addListener('cmap',       self.name, self._refresh)
+            def refresh(*a): self._refresh()
+
+            image.display.addListener('enabled',    self.name, refresh)
+            image.display.addListener('alpha',      self.name, refresh)
+            image.display.addListener('displayMin', self.name, refresh)
+            image.display.addListener('displayMax', self.name, refresh)
+            image.display.addListener('rangeClip',  self.name, refresh)
+            image.display.addListener('cmap',       self.name, refresh)
 
         self.Refresh()
 
@@ -380,13 +385,13 @@ class SliceCanvas(wxgl.GLCanvas, props.HasProperties):
         self.glReady = True
 
 
-    def _refresh(self, *a):
+    def _refresh(self, constraints=False):
         """
         Called when a display property changes. Updates x/y/z property
         values, updates the canvas bounding box, and triggers a redraw.
         """
         
-        self._setPropertyConstraints()
+        if constraints: self._setPropertyConstraints()
         self._calculateCanvasBBox(None)
         self.Refresh()
 
diff --git a/fsl/tools/fslview.py b/fsl/tools/fslview.py
index 11fd296ba8fc03d1bed1dbd6e1b0901a4ec79e41..c10c2ac852642bc2b3017fb34f3c26e157f8646a 100644
--- a/fsl/tools/fslview.py
+++ b/fsl/tools/fslview.py
@@ -11,6 +11,7 @@ import fsl.fslview.orthopanel     as orthopanel
 import fsl.fslview.imagelistpanel as imagelistpanel
 
 import fsl.data.fslimage as fslimage
+import fsl.props         as props
 
 
 class FslViewPanel(wx.Panel):
@@ -21,11 +22,13 @@ class FslViewPanel(wx.Panel):
         self.imageList = imageList
 
         self.orthoPanel = orthopanel    .OrthoPanel(    self, imageList)
+        self.ctrlPanel  = props.buildGUI(self, self.orthoPanel)
         self.listPanel  = imagelistpanel.ImageListPanel(self, imageList)
 
         self.sizer = wx.BoxSizer(wx.VERTICAL)
         self.SetSizer(self.sizer)
 
+        self.sizer.Add(self.ctrlPanel,  flag=wx.EXPAND)
         self.sizer.Add(self.orthoPanel, flag=wx.EXPAND, proportion=1)
         self.sizer.Add(self.listPanel,  flag=wx.EXPAND)