changeset 21824:6780a8657be3

Implement uibuttongroup (bug #47513) * doc/interpreter/genpropdoc.m: Wire in uibuttongroup documentation creation * doc/interpreter/gui.txi: Add documentation node for uibuttongroup * doc/interpreter/module.mk: Wire in uibuttongroup documentation creation * doc/interpreter/plot.txi: Add documentation node for uibuttongroup * libgui/graphics/__init_qt__.cc (__init__): Set default styling for uibuttongroup * libgui/graphics/Backend.cc (toolkitObjectProperty): Add uibuttongroup * libgui/graphics/Backend.cc (Backend::initialize): Add uibuttongroup * libgui/graphics/Backend.cc (Backend::update): Add uibuttongroup * libgui/graphics/ButtonControl.cc (ButtonControl::update): Catch forced uncheck of selected button in buttongroup * libgui/graphics/Canvas.cc (Canvas::select_object): Allow uibuttongroup to be selected like uicontrol or uipanel * libgui/graphics/Figure.cc (hasUiControlChildren): Ensure that uibuttongroup is known to have children * libgui/graphics/module.mk: Wire in ButtonGroup * libgui/graphics/ObjectFactory.cc (ObjectFactory::createObject): Create ButtonGroup for uibuttongroup * libgui/graphics/QtHandlesUtils.cc (computeFont<uibuttongroup>): Make computeFont template for uibuttongroup * libgui/graphics/RadioButtonControl.cc (RadioButtonControl::RadioButtonControl): On creation of button add to ButtonGroup * libgui/graphics/ToggleButtonControl.cc (ToggleButtonControl::ToggleButtonControl): On creation of button add to ButtonGroup * libinterp/corefcn/gl-render.cc (opengl_renderer::draw): Allow uibuttongroup to be drawn * libinterp/corefcn/gl-render.cc (opengl_renderer::draw_uibuttongroup): Method to draw uibuttongroup * libinterp/corefcn/gl-render.h (opengl_renderer::draw_uibuttongroup): Method to draw uibuttongroup * libinterp/corefcn/graphics.cc (lookup_object_name): Wire in uibuttongroup * libinterp/corefcn/graphics.cc (make_graphics_object_from_type): Wire in uibuttongroup * libinterp/corefcn/graphics.cc (property_list::set): Wire in uibuttongroup * libinterp/corefcn/graphics.cc (property_list::lookup): Wire in uibuttongroup * libinterp/corefcn/graphics.cc (uibuttongroup::properties::get_boundingbox uibuttongroup::properties::set_units uibuttongroup::properties::update_units uibuttongroup::properties::set_fontunits uibuttongroup::properties::update_fontunits uibuttongroup::properties::get_fontsize_points uibuttongroup::properties::set_selectedobject): Add property methods for uibuttongroup * libinterp/corefcn/graphics.in.h: Add uibuttongroup * scripts/gui/module.mk: Wire in uibuttongroup script * scripts/help/__unimplemented__.m: Remove uibuttongroup from unimplemented * libgui/graphics/ButtonGroup.cc: Add QT toolkit ButtonGroup object * libgui/graphics/ButtonGroup.h: Add QT toolkit ButtonGroup object * scripts/gui/uibuttongroup.m: Create uibuttongroup script
author Andrew Thornton <art27@cantab.net>
date Thu, 05 May 2016 20:03:26 +0100
parents 49d999dc443f
children f1f17f13b3b9
files doc/interpreter/genpropdoc.m doc/interpreter/gui.txi doc/interpreter/module.mk doc/interpreter/plot.txi libgui/graphics/Backend.cc libgui/graphics/ButtonControl.cc libgui/graphics/ButtonGroup.cc libgui/graphics/ButtonGroup.h libgui/graphics/Canvas.cc libgui/graphics/Figure.cc libgui/graphics/ObjectFactory.cc libgui/graphics/QtHandlesUtils.cc libgui/graphics/RadioButtonControl.cc libgui/graphics/ToggleButtonControl.cc libgui/graphics/__init_qt__.cc libgui/graphics/module.mk libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h scripts/gui/module.mk scripts/gui/uibuttongroup.m scripts/help/__unimplemented__.m
diffstat 23 files changed, 1064 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/doc/interpreter/genpropdoc.m	Fri Jun 03 20:56:33 2016 -0700
+++ b/doc/interpreter/genpropdoc.m	Thu May 05 20:03:26 2016 +0100
@@ -31,7 +31,7 @@
 function genpropdoc (objname, fname)
   objnames = {"root", "figure", "axes", "line", ...
               "text", "image", "patch", "surface", "light", ...
-              "uimenu", "uicontextmenu", "uipanel", ...
+              "uimenu", "uibuttongroup", "uicontextmenu", "uipanel", ...
               "uicontrol", "uitoolbar", "uipushtool", "uitoggletool"};
 
   ## Base properties
@@ -1374,6 +1374,33 @@
 
     endswitch
 
+  ## uibuttongroup properties
+  elseif (strcmp (objname, "uibuttongroup"))
+    switch (field)
+      ## Overridden shared properties
+
+      ## Specific properties
+        case "backgroundcolor"
+        case "bordertype"
+        case "borderwidth"
+        case "fontangle"
+        case "fontname"
+        case "fontsize"
+        case "fontunits"
+        case "fontweight"
+        case "foregroundcolor"
+        case "highlightcolor"
+        case "position"
+        case "resizefcn"
+        case "selectedobject"
+        case "selectionchangedfcn"
+        case "shadowcolor"
+        case "title"
+        case "titleposition"
+        case "units"
+
+    endswitch
+
   ## uicontrol properties
   elseif (strcmp (objname, "uicontrol"))
     switch (field)
--- a/doc/interpreter/gui.txi	Fri Jun 03 20:56:33 2016 -0700
+++ b/doc/interpreter/gui.txi	Thu May 05 20:03:26 2016 +0100
@@ -100,6 +100,8 @@
 
 @DOCSTRING(uimenu)
 
+@DOCSTRING(uibuttongroup)
+
 @DOCSTRING(uicontextmenu)
 
 @DOCSTRING(uicontrol)
@@ -149,4 +151,3 @@
 @DOCSTRING(ispref)
 
 @DOCSTRING(preferences)
-
--- a/doc/interpreter/module.mk	Fri Jun 03 20:56:33 2016 -0700
+++ b/doc/interpreter/module.mk	Thu May 05 20:03:26 2016 +0100
@@ -11,6 +11,7 @@
   doc/interpreter/plot-surfaceproperties.texi \
   doc/interpreter/plot-textproperties.texi \
   doc/interpreter/plot-uimenuproperties.texi \
+  doc/interpreter/plot-uibuttongroupproperties.texi \
   doc/interpreter/plot-uicontextmenuproperties.texi \
   doc/interpreter/plot-uipanelproperties.texi \
   doc/interpreter/plot-uicontrolproperties.texi \
@@ -56,6 +57,9 @@
 doc/interpreter/plot-uimenuproperties.texi: doc/interpreter/genpropdoc.m
 	$(AM_V_GEN)$(call gen-propdoc-texi,uimenu)
 
+doc/interpreter/plot-uibuttongroupproperties.texi: doc/interpreter/genpropdoc.m
+	$(AM_V_GEN)$(call gen-propdoc-texi,uibuttongroup)
+
 doc/interpreter/plot-uicontextmenuproperties.texi: doc/interpreter/genpropdoc.m
 	$(AM_V_GEN)$(call gen-propdoc-texi,uicontextmenu)
 
--- a/doc/interpreter/plot.txi	Fri Jun 03 20:56:33 2016 -0700
+++ b/doc/interpreter/plot.txi	Thu May 05 20:03:26 2016 +0100
@@ -1388,6 +1388,7 @@
 * Surface Properties::
 * Light Properties::
 * Uimenu Properties::
+* Uibuttongroup Properties::
 * Uicontextmenu Properties::
 * Uipanel Properties::
 * Uicontrol Properties::
@@ -1496,6 +1497,14 @@
 
 @include plot-uimenuproperties.texi
 
+@node Uibuttongroup Properties
+@subsubsection Uibuttongroup Properties
+@cindex uibuttongroup properties
+
+The @code{uibuttongroup} properties are:
+
+@include plot-uibuttongroupproperties.texi
+
 @node Uicontextmenu Properties
 @subsubsection Uicontextmenu Properties
 @cindex uicontextmenu properties
--- a/libgui/graphics/Backend.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/Backend.cc	Thu May 05 20:03:26 2016 +0100
@@ -55,6 +55,7 @@
     return std::string ("__plot_stream__");
   else if (go.isa ("uicontrol")
            || go.isa ("uipanel")
+           || go.isa ("uibuttongroup")
            || go.isa ("uimenu")
            || go.isa ("uicontextmenu")
            || go.isa ("uitoolbar")
@@ -87,6 +88,7 @@
   if (go.isa ("figure")
       || go.isa ("uicontrol")
       || go.isa ("uipanel")
+      || go.isa ("uibuttongroup")
       || go.isa ("uimenu")
       || go.isa ("uicontextmenu")
       || go.isa ("uitoolbar")
@@ -117,6 +119,7 @@
   if (pId == figure::properties::ID___PLOT_STREAM__
       || pId == uicontrol::properties::ID___OBJECT__
       || pId == uipanel::properties::ID___OBJECT__
+      || pId == uibuttongroup::properties::ID___OBJECT__
       || pId == uimenu::properties::ID___OBJECT__
       || pId == uicontextmenu::properties::ID___OBJECT__
       || pId == uitoolbar::properties::ID___OBJECT__
--- a/libgui/graphics/ButtonControl.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/ButtonControl.cc	Thu May 05 20:03:26 2016 +0100
@@ -27,6 +27,7 @@
 #include <QAbstractButton>
 
 #include "ButtonControl.h"
+#include "ButtonGroup.h"
 #include "Container.h"
 #include "QtHandlesUtils.h"
 
@@ -82,7 +83,16 @@
               if (dValue != 0.0 && dValue != 1.0)
                 warning ("button value not within valid display range");
               else if (dValue == up.get_min () && btn->isChecked ())
-                btn->setChecked (false);
+                {
+                  btn->setChecked (false);
+                  if (up.style_is ("radiobutton") || up.style_is ("togglebutton"))
+                    {
+                      Object* parent = Object::parentObject (gh_manager::get_object (up.get___myhandle__ ()));
+                      ButtonGroup* btnGroup = dynamic_cast<ButtonGroup*>(parent);
+                      if (btnGroup)
+                        btnGroup->selectNothing ();
+                    }
+                }
               else if (dValue == up.get_max () && ! btn->isChecked ())
                 btn->setChecked (true);
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/graphics/ButtonGroup.cc	Thu May 05 20:03:26 2016 +0100
@@ -0,0 +1,487 @@
+/*
+
+Copyright (C) 2016 Andrew Thornton
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <QAbstractButton>
+#include <QButtonGroup>
+#include <QEvent>
+#include <QFrame>
+#include <QLabel>
+#include <QMouseEvent>
+#include <QRadioButton>
+#include <QTimer>
+
+#include "Canvas.h"
+#include "Container.h"
+#include "ContextMenu.h"
+#include "ButtonGroup.h"
+#include "ToggleButtonControl.h"
+#include "RadioButtonControl.h"
+#include "Backend.h"
+#include "QtHandlesUtils.h"
+
+#include "ov-struct.h"
+
+namespace QtHandles
+{
+
+static int
+frameStyleFromProperties (const uibuttongroup::properties& pp)
+{
+    if (pp.bordertype_is ("none"))
+      return QFrame::NoFrame;
+    else if (pp.bordertype_is ("etchedin"))
+      return (QFrame::Box | QFrame::Sunken);
+    else if (pp.bordertype_is ("etchedout"))
+      return (QFrame::Box | QFrame::Raised);
+    else if (pp.bordertype_is ("beveledin"))
+      return (QFrame::Panel | QFrame::Sunken);
+    else if (pp.bordertype_is ("beveledout"))
+      return (QFrame::Panel | QFrame::Raised);
+    else
+      return (QFrame::Panel | QFrame::Plain);
+}
+
+static void
+setupPalette (const uibuttongroup::properties& pp, QPalette &p)
+{
+  p.setColor (QPalette::Window,
+              Utils::fromRgb (pp.get_backgroundcolor_rgb ()));
+  p.setColor (QPalette::WindowText,
+              Utils::fromRgb (pp.get_foregroundcolor_rgb ()));
+  p.setColor (QPalette::Light,
+              Utils::fromRgb (pp.get_highlightcolor_rgb ()));
+  p.setColor (QPalette::Dark,
+              Utils::fromRgb (pp.get_shadowcolor_rgb ()));
+}
+
+static int
+borderWidthFromProperties (const uibuttongroup::properties& pp)
+{
+  int bw = 0;
+
+  if (! pp.bordertype_is ("none"))
+    {
+      bw = octave::math::round (pp.get_borderwidth ());
+      if (pp.bordertype_is ("etchedin") || pp.bordertype_is ("etchedout"))
+        bw *= 2;
+    }
+
+  return bw;
+}
+
+ButtonGroup*
+ButtonGroup::create (const graphics_object& go)
+{
+  Object* parent = Object::parentObject (go);
+
+  if (parent)
+    {
+      Container* container = parent->innerContainer ();
+
+      if (container) {
+        QFrame* frame = new QFrame(container);
+        return new ButtonGroup (go, new QButtonGroup (frame), frame);
+      }
+    }
+
+  return 0;
+}
+
+ButtonGroup::ButtonGroup (const graphics_object& go, QButtonGroup* buttongroup, QFrame* frame)
+  : Object (go, frame), m_hiddenbutton(0), m_container (0), m_title (0), m_blockUpdates (false)
+{
+  uibuttongroup::properties& pp = properties<uibuttongroup> ();
+
+  frame->setObjectName ("UIButtonGroup");
+  frame->setAutoFillBackground (true);
+  Matrix bb = pp.get_boundingbox (false);
+  frame->setGeometry (octave::math::round (bb(0)), octave::math::round (bb(1)),
+                      octave::math::round (bb(2)), octave::math::round (bb(3)));
+  frame->setFrameStyle (frameStyleFromProperties (pp));
+  frame->setLineWidth (octave::math::round (pp.get_borderwidth ()));
+  QPalette pal = frame->palette ();
+  setupPalette (pp, pal);
+  frame->setPalette (pal);
+  m_buttongroup = buttongroup;
+  m_hiddenbutton = new QRadioButton (frame);
+  m_hiddenbutton->hide ();
+  m_buttongroup->addButton (m_hiddenbutton);
+
+  m_container = new Container (frame);
+  m_container->canvas (m_handle);
+
+  if (frame->hasMouseTracking ())
+    {
+      foreach (QWidget* w, frame->findChildren<QWidget*> ())
+        { w->setMouseTracking (true); }
+      foreach (QWidget* w, buttongroup->findChildren<QWidget*> ())
+        { w->setMouseTracking (true); }
+    }
+
+  QString title = Utils::fromStdString (pp.get_title ());
+  if (! title.isEmpty ())
+    {
+      m_title = new QLabel (title, frame);
+      m_title->setAutoFillBackground (true);
+      m_title->setContentsMargins (4, 0, 4, 0);
+      m_title->setPalette (pal);
+      m_title->setFont (Utils::computeFont<uibuttongroup> (pp, bb(3)));
+    }
+
+  frame->installEventFilter (this);
+  m_container->installEventFilter (this);
+
+  if (pp.is_visible ())
+    {
+      QTimer::singleShot (0, frame, SLOT (show (void)));
+      QTimer::singleShot (0, buttongroup, SLOT (show (void)));
+    }
+  else
+    frame->hide ();
+
+  connect (m_buttongroup, SIGNAL (buttonClicked (QAbstractButton*)), SLOT (buttonClicked (QAbstractButton*)));
+}
+
+ButtonGroup::~ButtonGroup (void)
+{
+}
+
+bool
+ButtonGroup::eventFilter (QObject* watched, QEvent* xevent)
+{
+  if (! m_blockUpdates)
+    {
+      if (watched == qObject ())
+        {
+          switch (xevent->type ())
+            {
+            case QEvent::Resize:
+                {
+                  gh_manager::auto_lock lock;
+                  graphics_object go = object ();
+
+                  if (go.valid_object ())
+                    {
+                      if (m_title)
+                        {
+                          const uibuttongroup::properties& pp =
+                            Utils::properties<uibuttongroup> (go);
+
+                          if (pp.fontunits_is ("normalized"))
+                            {
+                              QFrame* frame = qWidget<QFrame> ();
+
+                              m_title->setFont (Utils::computeFont<uibuttongroup>
+                                                (pp, frame->height ()));
+                              m_title->resize (m_title->sizeHint ());
+                            }
+                        }
+                      updateLayout ();
+                    }
+                }
+              break;
+
+            case QEvent::MouseButtonPress:
+                {
+                  QMouseEvent* m = dynamic_cast<QMouseEvent*> (xevent);
+
+                  if (m->button () == Qt::RightButton)
+                    {
+                      gh_manager::auto_lock lock;
+
+                      ContextMenu::executeAt (properties (), m->globalPos ());
+                    }
+                }
+              break;
+
+            default:
+              break;
+            }
+        }
+      else if (watched == m_container)
+        {
+          switch (xevent->type ())
+            {
+            case QEvent::Resize:
+              if (qWidget<QWidget> ()->isVisible ())
+                {
+                  gh_manager::auto_lock lock;
+
+                  properties ().update_boundingbox ();
+                }
+              break;
+
+            default:
+              break;
+            }
+        }
+    }
+
+  return false;
+}
+
+void
+ButtonGroup::update (int pId)
+{
+  uibuttongroup::properties& pp = properties<uibuttongroup> ();
+  QFrame* frame = qWidget<QFrame> ();
+
+  m_blockUpdates = true;
+
+  switch (pId)
+    {
+    case uibuttongroup::properties::ID_POSITION:
+      {
+        Matrix bb = pp.get_boundingbox (false);
+
+        frame->setGeometry (octave::math::round (bb(0)), octave::math::round (bb(1)),
+                            octave::math::round (bb(2)), octave::math::round (bb(3)));
+        updateLayout ();
+      }
+      break;
+
+    case uibuttongroup::properties::ID_BORDERWIDTH:
+      frame->setLineWidth (octave::math::round (pp.get_borderwidth ()));
+      updateLayout ();
+      break;
+
+    case uibuttongroup::properties::ID_BACKGROUNDCOLOR:
+    case uibuttongroup::properties::ID_FOREGROUNDCOLOR:
+    case uibuttongroup::properties::ID_HIGHLIGHTCOLOR:
+    case uibuttongroup::properties::ID_SHADOWCOLOR:
+      {
+        QPalette pal = frame->palette ();
+
+        setupPalette (pp, pal);
+        frame->setPalette (pal);
+        if (m_title)
+          m_title->setPalette (pal);
+      }
+      break;
+
+    case uibuttongroup::properties::ID_TITLE:
+      {
+        QString title = Utils::fromStdString (pp.get_title ());
+
+        if (title.isEmpty ())
+          {
+            if (m_title)
+              delete m_title;
+            m_title = 0;
+          }
+        else
+          {
+            if (! m_title)
+              {
+                QPalette pal = frame->palette ();
+
+                m_title = new QLabel (title, frame);
+                m_title->setAutoFillBackground (true);
+                m_title->setContentsMargins (4, 0, 4, 0);
+                m_title->setPalette (pal);
+                m_title->setFont (Utils::computeFont<uibuttongroup> (pp));
+                m_title->show ();
+              }
+            else
+              {
+                m_title->setText (title);
+                m_title->resize (m_title->sizeHint ());
+              }
+          }
+        updateLayout ();
+      }
+      break;
+
+    case uibuttongroup::properties::ID_TITLEPOSITION:
+      updateLayout ();
+      break;
+
+    case uibuttongroup::properties::ID_BORDERTYPE:
+      frame->setFrameStyle (frameStyleFromProperties (pp));
+      updateLayout ();
+      break;
+
+    case uibuttongroup::properties::ID_FONTNAME:
+    case uibuttongroup::properties::ID_FONTSIZE:
+    case uibuttongroup::properties::ID_FONTWEIGHT:
+    case uibuttongroup::properties::ID_FONTANGLE:
+      if (m_title)
+        {
+          m_title->setFont (Utils::computeFont<uibuttongroup> (pp));
+          m_title->resize (m_title->sizeHint ());
+          updateLayout ();
+        }
+      break;
+
+    case uibuttongroup::properties::ID_VISIBLE:
+      frame->setVisible (pp.is_visible ());
+      updateLayout ();
+      break;
+
+    case uibuttongroup::properties::ID_SELECTEDOBJECT:
+      {
+        graphics_handle h = pp.get_selectedobject ();
+        gh_manager::auto_lock lock;
+        graphics_object go = gh_manager::get_object (h);
+        Object* selectedObject = Backend::toolkitObject (go);
+        ToggleButtonControl* toggle = static_cast<ToggleButtonControl*> (selectedObject);
+        RadioButtonControl* radio = static_cast<RadioButtonControl*>(selectedObject);
+        if (toggle)
+          {
+            go.get_properties ().set ("value", 1);
+          }
+        else if (radio)
+          {
+            go.get_properties ().set ("value", 1);
+          }
+        else
+          {
+            m_hiddenbutton->setChecked (true);
+          }
+      }
+      break;
+
+    default:
+      break;
+    }
+
+  m_blockUpdates = false;
+}
+
+void
+ButtonGroup::redraw (void)
+{
+  Canvas* canvas = m_container->canvas (m_handle);
+
+  if (canvas)
+    canvas->redraw ();
+}
+
+void
+ButtonGroup::updateLayout (void)
+{
+  uibuttongroup::properties& pp = properties<uibuttongroup> ();
+  QFrame* frame = qWidget<QFrame> ();
+
+  Matrix bb = pp.get_boundingbox (true);
+  int bw = borderWidthFromProperties (pp);
+
+  frame->setFrameRect (QRect (octave::math::round (bb(0)) - bw, octave::math::round (bb(1)) - bw,
+                              octave::math::round (bb(2)) + 2*bw, octave::math::round (bb(3)) + 2*bw));
+  m_container->setGeometry (octave::math::round (bb(0)), octave::math::round (bb(1)),
+                            octave::math::round (bb(2)), octave::math::round (bb(3)));
+
+  if (m_blockUpdates)
+    pp.update_boundingbox ();
+
+  if (m_title)
+    {
+      QSize sz = m_title->sizeHint ();
+      int offset = 5;
+
+      if (pp.titleposition_is ("lefttop"))
+        m_title->move (bw+offset, 0);
+      else if (pp.titleposition_is ("righttop"))
+        m_title->move (frame->width () - bw - offset - sz.width (), 0);
+      else if (pp.titleposition_is ("leftbottom"))
+        m_title->move (bw+offset, frame->height () - sz.height ());
+      else if (pp.titleposition_is ("rightbottom"))
+        m_title->move (frame->width () - bw - offset - sz.width (),
+                       frame->height () - sz.height ());
+      else if (pp.titleposition_is ("centertop"))
+        m_title->move (frame->width () / 2 - sz.width () / 2, 0);
+      else if (pp.titleposition_is ("centerbottom"))
+        m_title->move (frame->width () / 2 - sz.width () / 2,
+                       frame->height () - sz.height ());
+    }
+}
+
+void
+ButtonGroup::selectNothing (void)
+{
+  m_hiddenbutton->setChecked (true);
+}
+
+
+void
+ButtonGroup::addButton (QAbstractButton* btn)
+{
+  m_buttongroup->addButton(btn);
+  connect (btn, SIGNAL (toggled (bool)), SLOT (buttonToggled (bool)));
+}
+
+void
+ButtonGroup::buttonToggled (bool toggled)
+{
+  Q_UNUSED (toggled);
+  if (!m_blockUpdates)
+  {
+    gh_manager::auto_lock lock;
+    uibuttongroup::properties& bp = properties<uibuttongroup> ();
+
+    graphics_handle oldValue = bp.get_selectedobject();
+
+    QAbstractButton* checkedBtn = m_buttongroup->checkedButton();
+
+    graphics_handle newValue = graphics_handle ();
+    if (checkedBtn != m_hiddenbutton)
+      {
+        Object* checkedObj = Object::fromQObject(checkedBtn);
+        newValue = checkedObj->properties ().get___myhandle__ ();
+      }
+
+    if (oldValue != newValue)
+      gh_manager::post_set (m_handle, "selectedobject", newValue.as_octave_value (), false);
+  }
+}
+
+void
+ButtonGroup::buttonClicked (QAbstractButton* btn)
+{
+  Q_UNUSED(btn);
+
+  gh_manager::auto_lock lock;
+  uibuttongroup::properties& bp = properties<uibuttongroup> ();
+
+  graphics_handle oldValue = bp.get_selectedobject();
+
+  QAbstractButton* checkedBtn = m_buttongroup->checkedButton();
+  Object* checkedObj = Object::fromQObject(checkedBtn);
+  graphics_handle newValue = checkedObj->properties ().get___myhandle__ ();
+
+  if (oldValue != newValue)
+    {
+      octave_scalar_map eventData;
+      eventData.setfield ("OldValue", oldValue.as_octave_value ());
+      eventData.setfield ("NewValue", newValue.as_octave_value ());
+      eventData.setfield ("Source", bp.get___myhandle__ ().as_octave_value ());
+      eventData.setfield ("EventName", "SelectionChanged");
+      octave_value selectionChangedEventObject = octave_value (new octave_struct (eventData));
+      gh_manager::post_callback(m_handle, "selectionchangedfcn", selectionChangedEventObject);
+    }
+}
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/graphics/ButtonGroup.h	Thu May 05 20:03:26 2016 +0100
@@ -0,0 +1,78 @@
+/*
+
+Copyright (C) 2016 Andrew Thornton
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if ! defined (octave_ButtonGroup_h)
+#define octave_ButtonGroup_h 1
+
+#include "Object.h"
+
+class QAbstractButton;
+class QButtonGroup;
+class QFrame;
+class QLabel;
+class QRadioButton;
+
+namespace QtHandles
+{
+
+class Container;
+
+class ButtonGroup : public Object
+{
+  Q_OBJECT
+
+public:
+  ButtonGroup (const graphics_object& go, QButtonGroup* buttongroup, QFrame* frame);
+  ~ButtonGroup (void);
+
+  Container* innerContainer (void) { return m_container; }
+
+  bool eventFilter (QObject* watched, QEvent* event);
+
+  static ButtonGroup* create (const graphics_object& go);
+
+  void addButton (QAbstractButton* btn);
+
+  void selectNothing (void);
+
+protected:
+  void update (int pId);
+  void redraw (void);
+
+private slots:
+  void buttonToggled (bool toggled);
+  void buttonClicked (QAbstractButton* btn);
+
+private:
+  void updateLayout (void);
+
+private:
+  QButtonGroup* m_buttongroup;
+  QRadioButton* m_hiddenbutton;
+  Container* m_container;
+  QLabel* m_title;
+  bool m_blockUpdates;
+};
+
+}; // namespace QtHandles
+
+#endif
--- a/libgui/graphics/Canvas.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/Canvas.cc	Thu May 05 20:03:26 2016 +0100
@@ -386,7 +386,8 @@
 
       if (childObj.isa ("axes"))
         axesList.append (childObj);
-      else if (childObj.isa ("uicontrol") || childObj.isa ("uipanel"))
+      else if (childObj.isa ("uicontrol") || childObj.isa ("uipanel")
+               || childObj.isa ("uibuttongroup"))
         {
           Matrix bb = childObj.get_properties ().get_boundingbox (false);
           QRectF r (bb(0), bb(1), bb(2), bb(3));
--- a/libgui/graphics/Figure.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/Figure.cc	Thu May 05 20:03:26 2016 +0100
@@ -371,12 +371,14 @@
     }
 
   foreach (QFrame* frame,
-           qWidget<QWidget> ()->findChildren<QFrame*> ("UIPanel"))
+           qWidget<QWidget> ()->findChildren<QFrame*> ())
     {
-      Object* obj = Object::fromQObject (frame);
+      if (frame->objectName () == "UIPanel" || frame->objectName () == "UIButtonGroup") {
+        Object* obj = Object::fromQObject (frame);
 
-      if (obj)
-        obj->slotRedraw ();
+        if (obj)
+          obj->slotRedraw ();
+      }
     }
 
   updateFigureToolBarAndMenuBar ();
--- a/libgui/graphics/ObjectFactory.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/ObjectFactory.cc	Thu May 05 20:03:26 2016 +0100
@@ -30,6 +30,7 @@
 #include "graphics.h"
 
 #include "Backend.h"
+#include "ButtonGroup.h"
 #include "CheckBoxControl.h"
 #include "ContextMenu.h"
 #include "EditControl.h"
@@ -119,6 +120,8 @@
                   else if (up.style_is ("listbox"))
                     obj = ListBoxControl::create (go);
                 }
+              else if (go.isa ("uibuttongroup"))
+                obj = ButtonGroup::create (go);
               else if (go.isa ("uipanel"))
                 obj = Panel::create (go);
               else if (go.isa ("uimenu"))
--- a/libgui/graphics/QtHandlesUtils.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/QtHandlesUtils.cc	Thu May 05 20:03:26 2016 +0100
@@ -135,6 +135,8 @@
 template QFont computeFont<uipanel> (const uipanel::properties& props,
                                      int height);
 
+template QFont computeFont<uibuttongroup> (const uibuttongroup::properties& props,
+                                           int height);
 QColor
 fromRgb (const Matrix& rgb)
 {
--- a/libgui/graphics/RadioButtonControl.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/RadioButtonControl.cc	Thu May 05 20:03:26 2016 +0100
@@ -26,6 +26,7 @@
 
 #include <QRadioButton>
 
+#include "ButtonGroup.h"
 #include "RadioButtonControl.h"
 #include "Container.h"
 #include "QtHandlesUtils.h"
@@ -53,6 +54,11 @@
                                         QRadioButton* radio)
   : ButtonControl (go, radio)
 {
+  Object* parent = Object::parentObject (go);
+  ButtonGroup* btnGroup = dynamic_cast<ButtonGroup*>(parent);
+  if (btnGroup)
+    btnGroup->addButton (radio);
+
   radio->setAutoFillBackground (true);
   radio->setAutoExclusive (false);
 }
--- a/libgui/graphics/ToggleButtonControl.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/ToggleButtonControl.cc	Thu May 05 20:03:26 2016 +0100
@@ -27,6 +27,7 @@
 #include <QPushButton>
 
 #include "ToggleButtonControl.h"
+#include "ButtonGroup.h"
 #include "Container.h"
 #include "QtHandlesUtils.h"
 
@@ -53,6 +54,11 @@
                                           QPushButton* btn)
     : ButtonControl (go, btn)
 {
+  Object* parent = Object::parentObject (go);
+  ButtonGroup* btnGroup = dynamic_cast<ButtonGroup*>(parent);
+  if (btnGroup)
+    btnGroup->addButton (btn);
+
   btn->setCheckable (true);
   btn->setAutoFillBackground (true);
 }
--- a/libgui/graphics/__init_qt__.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/__init_qt__.cc	Thu May 05 20:03:26 2016 +0100
@@ -83,6 +83,15 @@
                     octave_value (Utils::toRgb (p.color (QPalette::Light))));
           root.set ("defaultuipanelshadowcolor",
                     octave_value (Utils::toRgb (p.color (QPalette::Dark))));
+          root.set ("defaultuibuttongroupbackgroundcolor",
+                    octave_value (Utils::toRgb (p.color (QPalette::Window))));
+          root.set ("defaultuibuttongroupforegroundcolor",
+                    octave_value (Utils::toRgb
+                                  (p.color (QPalette::WindowText))));
+          root.set ("defaultuibuttongrouphighlightcolor",
+                    octave_value (Utils::toRgb (p.color (QPalette::Light))));
+          root.set ("defaultuibuttongroupshadowcolor",
+                    octave_value (Utils::toRgb (p.color (QPalette::Dark))));
 
           qtHandlesInitialized = true;
 
--- a/libgui/graphics/module.mk	Fri Jun 03 20:56:33 2016 -0700
+++ b/libgui/graphics/module.mk	Thu May 05 20:03:26 2016 +0100
@@ -4,6 +4,7 @@
   libgui/graphics/moc-annotation-dialog.cc \
   libgui/graphics/moc-Backend.cc \
   libgui/graphics/moc-ButtonControl.cc \
+  libgui/graphics/moc-ButtonGroup.cc \
   libgui/graphics/moc-ContextMenu.cc \
   libgui/graphics/moc-EditControl.cc \
   libgui/graphics/moc-Figure.cc \
@@ -48,6 +49,7 @@
   libgui/graphics/Backend.h \
   libgui/graphics/BaseControl.h \
   libgui/graphics/ButtonControl.h \
+  libgui/graphics/ButtonGroup.h \
   libgui/graphics/Canvas.h \
   libgui/graphics/CheckBoxControl.h \
   libgui/graphics/Container.h \
@@ -88,6 +90,7 @@
   libgui/graphics/Backend.cc \
   libgui/graphics/BaseControl.cc \
   libgui/graphics/ButtonControl.cc \
+  libgui/graphics/ButtonGroup.cc \
   libgui/graphics/Canvas.cc \
   libgui/graphics/CheckBoxControl.cc \
   libgui/graphics/Container.cc \
--- a/libinterp/corefcn/gl-render.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libinterp/corefcn/gl-render.cc	Thu May 05 20:03:26 2016 +0100
@@ -651,6 +651,11 @@
       if (toplevel)
         draw_uipanel (dynamic_cast<const uipanel::properties&> (props), go);
     }
+  else if (go.isa ("uibuttongroup"))
+    {
+      if (toplevel)
+        draw_uibuttongroup (dynamic_cast<const uibuttongroup::properties&> (props), go);
+    }
   else
     {
       warning ("opengl_renderer: cannot render object of type '%s'",
@@ -714,6 +719,24 @@
 }
 
 void
+opengl_renderer::draw_uibuttongroup (const uibuttongroup::properties& props,
+                                     const graphics_object& go)
+{
+  graphics_object fig = go.get_ancestor ("figure");
+  const figure::properties& figProps =
+    dynamic_cast<const figure::properties&> (fig.get_properties ());
+
+  // Initialize OpenGL context
+
+  init_gl_context (figProps.is___enhanced__ (),
+                   props.get_backgroundcolor_rgb ());
+
+  // Draw children
+
+  draw (props.get_all_children (), false);
+}
+
+void
 opengl_renderer::init_gl_context (bool enhanced, const Matrix& c)
 {
 #if defined (HAVE_OPENGL)
--- a/libinterp/corefcn/gl-render.h	Fri Jun 03 20:56:33 2016 -0700
+++ b/libinterp/corefcn/gl-render.h	Thu May 05 20:03:26 2016 +0100
@@ -70,7 +70,8 @@
   virtual void draw_image (const image::properties& props);
   virtual void draw_uipanel (const uipanel::properties& props,
                              const graphics_object& go);
-
+  virtual void draw_uibuttongroup (const uibuttongroup::properties& props,
+                                   const graphics_object& go);
   virtual void init_gl_context (bool enhanced, const Matrix& backgroundColor);
   virtual void setup_opengl_transformation (const axes::properties& props);
 
--- a/libinterp/corefcn/graphics.cc	Fri Jun 03 20:56:33 2016 -0700
+++ b/libinterp/corefcn/graphics.cc	Thu May 05 20:03:26 2016 +0100
@@ -1071,7 +1071,8 @@
                                 {
                                   pfx = name.substr (0, 13);
 
-                                  if (pfx.compare ("uicontextmenu"))
+                                  if (pfx.compare ("uicontextmenu") ||
+                                      pfx.compare ("uibuttongroup"))
                                     offset = 13;
                                 }
                             }
@@ -1123,6 +1124,8 @@
     go = new uicontrol (h, p);
   else if (type.compare ("uipanel"))
     go = new uipanel (h, p);
+  else if (type.compare ("uibuttongroup"))
+    go = new uibuttongroup (h, p);
   else if (type.compare ("uicontextmenu"))
     go = new uicontextmenu (h, p);
   else if (type.compare ("uitoolbar"))
@@ -1985,7 +1988,8 @@
                                 {
                                   pfx = name.substr (0, 13);
 
-                                  if (pfx.compare ("uicontextmenu"))
+                                  if (pfx.compare ("uicontextmenu")
+                                      || pfx.compare ("uibuttongroup"))
                                     offset = 13;
                                 }
                             }
@@ -2026,6 +2030,8 @@
             has_property = uimenu::properties::has_core_property (pname);
           else if (pfx == "uicontrol")
             has_property = uicontrol::properties::has_core_property (pname);
+          else if (pfx == "uibuttongroup")
+            has_property = uibuttongroup::properties::has_core_property (pname);
           else if (pfx == "uipanel")
             has_property = uipanel::properties::has_core_property (pname);
           else if (pfx == "uicontextmenu")
@@ -2122,7 +2128,8 @@
                                 {
                                   pfx = name.substr (0, 13);
 
-                                  if (pfx.compare ("uicontextmenu"))
+                                  if (pfx.compare ("uicontextmenu")
+                                      || pfx.compare ("uibuttongroup"))
                                     offset = 13;
                                 }
                             }
@@ -8820,6 +8827,176 @@
 // ---------------------------------------------------------------------
 
 Matrix
+uibuttongroup::properties::get_boundingbox (bool internal,
+                                            const Matrix& parent_pix_size) const
+{
+  Matrix pos = get_position ().matrix_value ();
+  Matrix parent_size (parent_pix_size);
+
+  if (parent_size.is_empty ())
+    {
+      graphics_object go = gh_manager::get_object (get_parent ());
+
+      parent_size =
+        go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
+    }
+
+  pos = convert_position (pos, get_units (), "pixels", parent_size);
+
+  pos(0)--;
+  pos(1)--;
+  pos(1) = parent_size(1) - pos(1) - pos(3);
+
+  if (internal)
+    {
+      double outer_height = pos(3);
+
+      pos(0) = pos(1) = 0;
+
+      if (! bordertype_is ("none"))
+        {
+          double bw = get_borderwidth ();
+          double mul = 1.0;
+
+          if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
+            mul = 2.0;
+
+          pos(0) += mul * bw;
+          pos(1) += mul * bw;
+          pos(2) -= 2 * mul * bw;
+          pos(3) -= 2 * mul * bw;
+        }
+
+      if (! get_title ().empty ())
+        {
+          double fontsz = get_fontsize ();
+
+          if (! fontunits_is ("pixels"))
+            {
+              double res = xget (0, "screenpixelsperinch").double_value ();
+
+              if (fontunits_is ("points"))
+                fontsz *= (res / 72.0);
+              else if (fontunits_is ("inches"))
+                fontsz *= res;
+              else if (fontunits_is ("centimeters"))
+                fontsz *= (res / 2.54);
+              else if (fontunits_is ("normalized"))
+                fontsz *= outer_height;
+            }
+
+          if (titleposition_is ("lefttop") || titleposition_is ("centertop")
+              || titleposition_is ("righttop"))
+            pos(1) += (fontsz / 2);
+          pos(3) -= (fontsz / 2);
+        }
+    }
+
+  return pos;
+}
+
+void
+uibuttongroup::properties::set_units (const octave_value& val)
+{
+  caseless_str old_units = get_units ();
+
+  if (units.set (val, true))
+    {
+      update_units (old_units);
+      mark_modified ();
+    }
+}
+
+void
+uibuttongroup::properties::update_units (const caseless_str& old_units)
+{
+  Matrix pos = get_position ().matrix_value ();
+
+  graphics_object parent_go = gh_manager::get_object (get_parent ());
+  Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
+  Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
+
+  pos = convert_position (pos, old_units, get_units (), parent_size);
+  set_position (pos);
+}
+
+void
+uibuttongroup::properties::set_fontunits (const octave_value& val)
+{
+  caseless_str old_fontunits = get_fontunits ();
+
+  if (fontunits.set (val, true))
+    {
+      update_fontunits (old_fontunits);
+      mark_modified ();
+    }
+}
+
+void
+uibuttongroup::properties::update_fontunits (const caseless_str& old_units)
+{
+  caseless_str new_units = get_fontunits ();
+  double parent_height = get_boundingbox (false).elem (3);
+  double fontsz = get_fontsize ();
+
+  fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
+
+  set_fontsize (octave_value (fontsz));
+}
+
+double
+uibuttongroup::properties::get_fontsize_points (double box_pix_height) const
+{
+  double fontsz = get_fontsize ();
+  double parent_height = box_pix_height;
+
+  if (fontunits_is ("normalized") && parent_height <= 0)
+    parent_height = get_boundingbox (false).elem (3);
+
+  return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
+}
+
+void
+uibuttongroup::properties::set_selectedobject (const octave_value& v)
+{
+  graphics_handle current_selectedobject = get_selectedobject();
+  selectedobject = current_selectedobject;
+  if (v.is_empty ())
+    {
+      if (current_selectedobject.ok ())
+        {
+          selectedobject = graphics_handle ();
+          mark_modified ();
+        }
+      return;
+    }
+
+  graphics_handle val (v);
+  if (val.ok ())
+    {
+      graphics_object go (gh_manager::get_object (val));
+      base_properties& gop = go.get_properties ();
+
+      if (go.valid_object ()
+          && gop.get_parent () == get___myhandle__ ()
+          && go.isa ("uicontrol"))
+        {
+          uicontrol::properties& cop = dynamic_cast<uicontrol::properties&> (go.get_properties ());
+          const caseless_str& style = cop.get_style ();
+          if (style.compare ("radiobutton") || style.compare ("togglebutton"))
+            {
+              selectedobject = val;
+              mark_modified ();
+              return;
+            }
+        }
+    }
+  err_set_invalid ("selectedobject");
+}
+
+// ---------------------------------------------------------------------
+
+Matrix
 uipanel::properties::get_boundingbox (bool internal,
                                       const Matrix& parent_pix_size) const
 {
@@ -9539,6 +9716,7 @@
   plist_map["hggroup"] = hggroup::properties::factory_defaults ();
   plist_map["uimenu"] = uimenu::properties::factory_defaults ();
   plist_map["uicontrol"] = uicontrol::properties::factory_defaults ();
+  plist_map["uibuttongroup"] = uibuttongroup::properties::factory_defaults ();
   plist_map["uipanel"] = uipanel::properties::factory_defaults ();
   plist_map["uicontextmenu"] = uicontextmenu::properties::factory_defaults ();
   plist_map["uitoolbar"] = uitoolbar::properties::factory_defaults ();
@@ -10395,6 +10573,15 @@
   GO_BODY (uicontrol);
 }
 
+DEFUN (__go_uibuttongroup__, args, ,
+       "-*- texinfo -*-\n\
+@deftypefn {} {} __go_uibuttongroup__ (@var{parent})\n\
+Undocumented internal function.\n\
+@end deftypefn")
+{
+  GO_BODY (uibuttongroup);
+}
+
 DEFUN (__go_uipanel__, args, ,
        "-*- texinfo -*-\n\
 @deftypefn {} {} __go_uipanel__ (@var{parent})\n\
@@ -11526,4 +11713,3 @@
 
   return ovl ();
 }
-
--- a/libinterp/corefcn/graphics.in.h	Fri Jun 03 20:56:33 2016 -0700
+++ b/libinterp/corefcn/graphics.in.h	Thu May 05 20:03:26 2016 +0100
@@ -5568,6 +5568,91 @@
 
 // ---------------------------------------------------------------------
 
+class OCTINTERP_API uibuttongroup : public base_graphics_object
+{
+public:
+  class OCTINTERP_API properties : public base_properties
+  {
+  public:
+    Matrix get_boundingbox (bool internal = false,
+                            const Matrix& parent_pix_size = Matrix ()) const;
+
+    double get_fontsize_points (double box_pix_height = 0) const;
+
+    // See the genprops.awk script for an explanation of the
+    // properties declarations.
+    // Programming note: Keep property list sorted if new ones are added.
+
+    BEGIN_PROPERTIES (uibuttongroup)
+      any_property __object__ h , Matrix ()
+      color_property backgroundcolor , color_values (1, 1, 1)
+      radio_property bordertype , "none|{etchedin}|etchedout|beveledin|beveledout|line"
+      double_property borderwidth , 1
+      bool_property clipping , "on"
+      radio_property fontangle , "{normal}|italic|oblique"
+      string_property fontname , OCTAVE_DEFAULT_FONTNAME
+      double_property fontsize , 10
+      radio_property fontunits S , "inches|centimeters|normalized|{points}|pixels"
+      radio_property fontweight , "light|{normal}|demi|bold"
+      color_property foregroundcolor , color_values (0, 0, 0)
+      color_property highlightcolor , color_values (1, 1, 1)
+      array_property position , default_panel_position ()
+      callback_property resizefcn , Matrix ()
+      handle_property selectedobject S , graphics_handle()
+      callback_property selectionchangedfcn , Matrix()
+      color_property shadowcolor , color_values (0, 0, 0)
+      callback_property sizechangedfcn , Matrix ()
+      radio_property units S , "{normalized}|inches|centimeters|points|pixels|characters"
+      string_property title , ""
+      radio_property titleposition , "{lefttop}|centertop|righttop|leftbottom|centerbottom|rightbottom"
+    END_PROPERTIES
+
+  protected:
+    void init (void)
+    {
+      position.add_constraint (dim_vector (1, 4));
+    }
+
+    // void update_text_extent (void);
+    // void update_string (void) { update_text_extent (); }
+    // void update_fontname (void) { update_text_extent (); }
+    // void update_fontsize (void) { update_text_extent (); }
+    // void update_fontangle (void) { update_text_extent (); }
+    // void update_fontweight (void) { update_text_extent (); }
+
+    void update_units (const caseless_str& old_units);
+    void update_fontunits (const caseless_str& old_units);
+
+  };
+
+private:
+  properties xproperties;
+
+public:
+  uibuttongroup (const graphics_handle& mh, const graphics_handle& p)
+    : base_graphics_object (), xproperties (mh, p)
+  { }
+
+  ~uibuttongroup (void) { }
+
+  base_properties& get_properties (void) { return xproperties; }
+
+  const base_properties& get_properties (void) const { return xproperties; }
+
+  bool valid_object (void) const { return true; }
+
+  bool has_readonly_property (const caseless_str& pname) const
+  {
+    bool retval = xproperties.has_readonly_property (pname);
+    if (! retval)
+      retval = base_properties::has_readonly_property (pname);
+    return retval;
+  }
+
+};
+
+// ---------------------------------------------------------------------
+
 class OCTINTERP_API uipanel : public base_graphics_object
 {
 public:
--- a/scripts/gui/module.mk	Fri Jun 03 20:56:33 2016 -0700
+++ b/scripts/gui/module.mk	Thu May 05 20:03:26 2016 +0100
@@ -21,6 +21,7 @@
   scripts/gui/listdlg.m \
   scripts/gui/msgbox.m \
   scripts/gui/questdlg.m \
+  scripts/gui/uibuttongroup.m \
   scripts/gui/uicontextmenu.m \
   scripts/gui/uicontrol.m \
   scripts/gui/uigetdir.m \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/gui/uibuttongroup.m	Thu May 05 20:03:26 2016 +0100
@@ -0,0 +1,104 @@
+## Copyright (C) 2016 Andrew Thornton
+##
+## This file is part of Octave.
+##
+## Octave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{hui} =} uibuttongroup (@var{property}, @var{value}, @dots{})
+## @deftypefnx {} {@var{hui} =} uibuttongroup (@var{parent}, @var{property}, @var{value}, @dots{})
+## @deftypefnx {} {} uibuttongroup (@var{h})
+##
+## Create a uibuttongroup object and return a handle to it.
+##
+## uibuttongroups are used to create group uicontrols.
+##
+## If @var{parent} is omitted then a uibuttongroup for the current figure is
+## created.  If no figure is available, a new figure is created first.
+##
+## If @var{parent} is given then a uibuttongroup relative to @var{parent} is
+## created.
+##
+## Any provided property value pairs will override the default values of the
+## created uibuttongroup object.
+##
+## Uibuttongroup properties are documented at @ref{Uibuttongroup Properties}.
+##
+## Examples:
+##
+## @example
+## @group
+## % create figure and panel on it
+## f = figure;
+## % create a button group
+## gp = uibuttongroup (f, "Position", [ 0 0.5 1 1])
+## % create a buttons in the group
+## b1 = uicontrol (gp, "style", "radiobutton", ...
+##                 "string", "Choice 1", ...
+##                 "Position", [ 10 150 100 50 ]);
+## b2 = uicontrol (gp, "style", "radiobutton", ...
+##                 "string", "Choice 2", ...
+##                 "Position", [ 10 50 100 30 ]);
+## % create a button not in the group
+## b3 = uicontrol (f, "style", "radiobutton", ...
+##                 "string", "Not in the group", ...
+##                 "Position", [ 10 50 100 50 ]);
+## @end group
+## @end example
+## @seealso{figure, uipanel}
+## @end deftypefn
+
+## Author: zeripath
+
+function hui = uibuttongroup (varargin)
+
+  if (nargin == 1 && ishandle (varargin{1})
+      && strcmpi (get (varargin{1}, "type"), "uibuttongroup"))
+    error ("uibuttongroup: focusing not implemented yet");
+  endif
+
+  [h, args] = __uiobject_split_args__ ("uibuttongroup", varargin,
+                                       {"figure", "uipanel", "uibuttongroup"});
+  hui = __go_uibuttongroup__ (h, args{:});
+
+endfunction
+
+%!demo
+%! f = figure;
+%! gp = uibuttongroup (f, "Position", [ 0 0.5 1 1], ...
+%!                     "selectionchangedfcn", ...
+%!                     @(x, y) display (['Selection Changed: ' get(y.NewValue, 'String')]));
+%! b1 = uicontrol (gp, "style", "radiobutton", ...
+%!                 "string", "Choice 1", ...
+%!                 "Position", [ 10 150 100 50 ]);
+%! b2 = uicontrol (gp, "style", "radiobutton", ...
+%!                 "string", "Choice 2", ...
+%!                 "Position", [ 10 50 100 30 ]);
+%! b3 = uicontrol (f, "style", "radiobutton", ...
+%!                 "string", "Not in the group", ...
+%!                 "Position", [ 10 50 100 50 ]);
+%! disp (['Current selected: ' get(get(gp, 'selectedobject'), 'String')]);
+%! pause (0.5);
+%! disp (['Select None']);
+%! set (gp, 'selectedobject', []);
+%! pause (0.1);
+%! disp (['Current selected: ' get(get(gp, 'selectedobject'), 'String')]);
+%! pause (0.5);
+%! disp (['Select b1']);
+%! set (gp, 'selectedobject', b1);
+%! disp (['Current selected: ' get(get(gp, 'selectedobject'), 'String')]);
+
+## Uncertain if tests can be performed
+%!assert (1)
--- a/scripts/help/__unimplemented__.m	Fri Jun 03 20:56:33 2016 -0700
+++ b/scripts/help/__unimplemented__.m	Thu May 05 20:03:26 2016 +0100
@@ -845,7 +845,6 @@
   "toolboxdir",
   "triangulation",
   "tscollection",
-  "uibuttongroup",
   "uigetpref",
   "uiimport",
   "uiopen",
@@ -900,4 +899,3 @@
 %! assert (str(1:51), "quad2d is not implemented.  Consider using dblquad.");
 %! str = __unimplemented__ ("MException");
 %! assert (str(1:58), "the 'MException' function is not yet implemented in Octave");
-