1 //////////////////////////////////////////////////////////////////////// 2 // 3 // Copyright (C) 2016-2021 The Octave Project Developers 4 // 5 // See the file COPYRIGHT.md in the top-level directory of this 6 // distribution or <https://octave.org/copyright/>. 7 // 8 // This file is part of Octave. 9 // 10 // Octave is free software: you can redistribute it and/or modify it 11 // under the terms of the GNU General Public License as published by 12 // the Free Software Foundation, either version 3 of the License, or 13 // (at your option) any later version. 14 // 15 // Octave is distributed in the hope that it will be useful, but 16 // WITHOUT ANY WARRANTY; without even the implied warranty of 17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 // GNU General Public License for more details. 19 // 20 // You should have received a copy of the GNU General Public License 21 // along with Octave; see the file COPYING. If not, see 22 // <https://www.gnu.org/licenses/>. 23 // 24 //////////////////////////////////////////////////////////////////////// 25 26 #if defined (HAVE_CONFIG_H) 27 # include "config.h" 28 #endif 29 30 #include <QAbstractButton> 31 #include <QButtonGroup> 32 #include <QEvent> 33 #include <QFrame> 34 #include <QLabel> 35 #include <QMouseEvent> 36 #include <QRadioButton> 37 #include <QTimer> 38 39 #include "Canvas.h" 40 #include "Container.h" 41 #include "ContextMenu.h" 42 #include "ButtonGroup.h" 43 #include "ToggleButtonControl.h" 44 #include "RadioButtonControl.h" 45 #include "QtHandlesUtils.h" 46 #include "qt-graphics-toolkit.h" 47 48 #include "octave-qobject.h" 49 50 #include "interpreter.h" 51 #include "ov-struct.h" 52 53 namespace QtHandles 54 { 55 56 static int frameStyleFromProperties(const uibuttongroup::properties & pp)57 frameStyleFromProperties (const uibuttongroup::properties& pp) 58 { 59 if (pp.bordertype_is ("none")) 60 return QFrame::NoFrame; 61 else if (pp.bordertype_is ("etchedin")) 62 return (QFrame::Box | QFrame::Sunken); 63 else if (pp.bordertype_is ("etchedout")) 64 return (QFrame::Box | QFrame::Raised); 65 else if (pp.bordertype_is ("beveledin")) 66 return (QFrame::Panel | QFrame::Sunken); 67 else if (pp.bordertype_is ("beveledout")) 68 return (QFrame::Panel | QFrame::Raised); 69 else 70 return (QFrame::Panel | QFrame::Plain); 71 } 72 73 static void setupPalette(const uibuttongroup::properties & pp,QPalette & p)74 setupPalette (const uibuttongroup::properties& pp, QPalette& p) 75 { 76 p.setColor (QPalette::Window, 77 Utils::fromRgb (pp.get_backgroundcolor_rgb ())); 78 p.setColor (QPalette::WindowText, 79 Utils::fromRgb (pp.get_foregroundcolor_rgb ())); 80 p.setColor (QPalette::Light, 81 Utils::fromRgb (pp.get_highlightcolor_rgb ())); 82 p.setColor (QPalette::Dark, 83 Utils::fromRgb (pp.get_shadowcolor_rgb ())); 84 } 85 86 static int borderWidthFromProperties(const uibuttongroup::properties & pp)87 borderWidthFromProperties (const uibuttongroup::properties& pp) 88 { 89 int bw = 0; 90 91 if (! pp.bordertype_is ("none")) 92 { 93 bw = octave::math::round (pp.get_borderwidth ()); 94 if (pp.bordertype_is ("etchedin") || pp.bordertype_is ("etchedout")) 95 bw *= 2; 96 } 97 98 return bw; 99 } 100 101 ButtonGroup* create(octave::base_qobject & oct_qobj,octave::interpreter & interp,const graphics_object & go)102 ButtonGroup::create (octave::base_qobject& oct_qobj, 103 octave::interpreter& interp, const graphics_object& go) 104 { 105 Object *parent = parentObject (interp, go); 106 107 if (parent) 108 { 109 Container *container = parent->innerContainer (); 110 111 if (container) 112 { 113 QFrame *frame = new QFrame (container); 114 return new ButtonGroup (oct_qobj, interp, go, 115 new QButtonGroup (frame), frame); 116 } 117 } 118 119 return nullptr; 120 } 121 ButtonGroup(octave::base_qobject & oct_qobj,octave::interpreter & interp,const graphics_object & go,QButtonGroup * buttongroup,QFrame * frame)122 ButtonGroup::ButtonGroup (octave::base_qobject& oct_qobj, 123 octave::interpreter& interp, 124 const graphics_object& go, 125 QButtonGroup *buttongroup, QFrame *frame) 126 : Object (oct_qobj, interp, go, frame), m_hiddenbutton (nullptr), 127 m_container (nullptr), m_title (nullptr), m_blockUpdates (false) 128 { 129 uibuttongroup::properties& pp = properties<uibuttongroup> (); 130 131 frame->setObjectName ("UIButtonGroup"); 132 frame->setAutoFillBackground (true); 133 Matrix bb = pp.get_boundingbox (false); 134 frame->setGeometry (octave::math::round (bb(0)), octave::math::round (bb(1)), 135 octave::math::round (bb(2)), octave::math::round (bb(3))); 136 frame->setFrameStyle (frameStyleFromProperties (pp)); 137 frame->setLineWidth (octave::math::round (pp.get_borderwidth ())); 138 QPalette pal = frame->palette (); 139 setupPalette (pp, pal); 140 frame->setPalette (pal); 141 m_buttongroup = buttongroup; 142 m_hiddenbutton = new QRadioButton (frame); 143 m_hiddenbutton->hide (); 144 m_buttongroup->addButton (m_hiddenbutton); 145 146 m_container = new Container (frame, oct_qobj, interp); 147 m_container->canvas (m_handle); 148 149 connect (m_container, SIGNAL (interpeter_event (const fcn_callback&)), 150 this, SIGNAL (interpeter_event (const fcn_callback&))); 151 152 connect (m_container, SIGNAL (interpeter_event (const meth_callback&)), 153 this, SIGNAL (interpeter_event (const meth_callback&))); 154 155 if (frame->hasMouseTracking ()) 156 { 157 for (auto *w : frame->findChildren<QWidget*> ()) 158 w->setMouseTracking (true); 159 for (auto *w : buttongroup->findChildren<QWidget*> ()) 160 w->setMouseTracking (true); 161 } 162 163 QString title = Utils::fromStdString (pp.get_title ()); 164 if (! title.isEmpty ()) 165 { 166 m_title = new QLabel (title, frame); 167 m_title->setAutoFillBackground (true); 168 m_title->setContentsMargins (4, 0, 4, 0); 169 m_title->setPalette (pal); 170 m_title->setFont (Utils::computeFont<uibuttongroup> (pp, bb(3))); 171 } 172 173 frame->installEventFilter (this); 174 m_container->installEventFilter (this); 175 176 if (pp.is_visible ()) 177 { 178 QTimer::singleShot (0, frame, SLOT (show (void))); 179 QTimer::singleShot (0, buttongroup, SLOT (show (void))); 180 } 181 else 182 frame->hide (); 183 184 connect (m_buttongroup, SIGNAL (buttonClicked (QAbstractButton*)), 185 SLOT (buttonClicked (QAbstractButton*))); 186 } 187 ~ButtonGroup(void)188 ButtonGroup::~ButtonGroup (void) 189 { } 190 191 bool eventFilter(QObject * watched,QEvent * xevent)192 ButtonGroup::eventFilter (QObject *watched, QEvent *xevent) 193 { 194 if (! m_blockUpdates) 195 { 196 gh_manager& gh_mgr = m_interpreter.get_gh_manager (); 197 198 if (watched == qObject ()) 199 { 200 switch (xevent->type ()) 201 { 202 case QEvent::Resize: 203 { 204 octave::autolock guard (gh_mgr.graphics_lock ()); 205 206 graphics_object go = object (); 207 208 if (go.valid_object ()) 209 { 210 if (m_title) 211 { 212 const uibuttongroup::properties& pp = 213 Utils::properties<uibuttongroup> (go); 214 215 if (pp.fontunits_is ("normalized")) 216 { 217 QFrame *frame = qWidget<QFrame> (); 218 219 m_title->setFont (Utils::computeFont<uibuttongroup> 220 (pp, frame->height ())); 221 m_title->resize (m_title->sizeHint ()); 222 } 223 } 224 updateLayout (); 225 } 226 } 227 break; 228 229 case QEvent::MouseButtonPress: 230 { 231 QMouseEvent *m = dynamic_cast<QMouseEvent *> (xevent); 232 233 if (m->button () == Qt::RightButton) 234 { 235 octave::autolock guard (gh_mgr.graphics_lock ()); 236 237 ContextMenu::executeAt (m_interpreter, properties (), 238 m->globalPos ()); 239 } 240 } 241 break; 242 243 default: 244 break; 245 } 246 } 247 else if (watched == m_container) 248 { 249 switch (xevent->type ()) 250 { 251 case QEvent::Resize: 252 if (qWidget<QWidget> ()->isVisible ()) 253 { 254 octave::autolock guard (gh_mgr.graphics_lock ()); 255 256 properties ().update_boundingbox (); 257 } 258 break; 259 260 default: 261 break; 262 } 263 } 264 } 265 266 return false; 267 } 268 269 void update(int pId)270 ButtonGroup::update (int pId) 271 { 272 uibuttongroup::properties& pp = properties<uibuttongroup> (); 273 QFrame *frame = qWidget<QFrame> (); 274 275 m_blockUpdates = true; 276 277 switch (pId) 278 { 279 case uibuttongroup::properties::ID_POSITION: 280 { 281 Matrix bb = pp.get_boundingbox (false); 282 283 frame->setGeometry (octave::math::round (bb(0)), octave::math::round (bb(1)), 284 octave::math::round (bb(2)), octave::math::round (bb(3))); 285 updateLayout (); 286 } 287 break; 288 289 case uibuttongroup::properties::ID_BORDERWIDTH: 290 frame->setLineWidth (octave::math::round (pp.get_borderwidth ())); 291 updateLayout (); 292 break; 293 294 case uibuttongroup::properties::ID_BACKGROUNDCOLOR: 295 case uibuttongroup::properties::ID_FOREGROUNDCOLOR: 296 case uibuttongroup::properties::ID_HIGHLIGHTCOLOR: 297 case uibuttongroup::properties::ID_SHADOWCOLOR: 298 { 299 QPalette pal = frame->palette (); 300 301 setupPalette (pp, pal); 302 frame->setPalette (pal); 303 if (m_title) 304 m_title->setPalette (pal); 305 } 306 break; 307 308 case uibuttongroup::properties::ID_TITLE: 309 { 310 QString title = Utils::fromStdString (pp.get_title ()); 311 312 if (title.isEmpty ()) 313 { 314 if (m_title) 315 delete m_title; 316 m_title = nullptr; 317 } 318 else 319 { 320 if (! m_title) 321 { 322 QPalette pal = frame->palette (); 323 324 m_title = new QLabel (title, frame); 325 m_title->setAutoFillBackground (true); 326 m_title->setContentsMargins (4, 0, 4, 0); 327 m_title->setPalette (pal); 328 m_title->setFont (Utils::computeFont<uibuttongroup> (pp)); 329 m_title->show (); 330 } 331 else 332 { 333 m_title->setText (title); 334 m_title->resize (m_title->sizeHint ()); 335 } 336 } 337 updateLayout (); 338 } 339 break; 340 341 case uibuttongroup::properties::ID_TITLEPOSITION: 342 updateLayout (); 343 break; 344 345 case uibuttongroup::properties::ID_BORDERTYPE: 346 frame->setFrameStyle (frameStyleFromProperties (pp)); 347 updateLayout (); 348 break; 349 350 case uibuttongroup::properties::ID_FONTNAME: 351 case uibuttongroup::properties::ID_FONTSIZE: 352 case uibuttongroup::properties::ID_FONTWEIGHT: 353 case uibuttongroup::properties::ID_FONTANGLE: 354 if (m_title) 355 { 356 m_title->setFont (Utils::computeFont<uibuttongroup> (pp)); 357 m_title->resize (m_title->sizeHint ()); 358 updateLayout (); 359 } 360 break; 361 362 case uibuttongroup::properties::ID_VISIBLE: 363 frame->setVisible (pp.is_visible ()); 364 updateLayout (); 365 break; 366 367 case uibuttongroup::properties::ID_SELECTEDOBJECT: 368 { 369 graphics_handle h = pp.get_selectedobject (); 370 371 gh_manager& gh_mgr = m_interpreter.get_gh_manager (); 372 373 octave::autolock guard (gh_mgr.graphics_lock ()); 374 375 graphics_object go = gh_mgr.get_object (h); 376 377 Object *selectedObject = qt_graphics_toolkit::toolkitObject (go); 378 ToggleButtonControl *toggle = static_cast<ToggleButtonControl *> 379 (selectedObject); 380 RadioButtonControl *radio = static_cast<RadioButtonControl *>(selectedObject); 381 if (toggle) 382 { 383 go.get_properties ().set ("value", 1); 384 } 385 else if (radio) 386 { 387 go.get_properties ().set ("value", 1); 388 } 389 else 390 { 391 m_hiddenbutton->setChecked (true); 392 } 393 } 394 break; 395 396 default: 397 break; 398 } 399 400 m_blockUpdates = false; 401 } 402 403 void redraw(void)404 ButtonGroup::redraw (void) 405 { 406 update (uibuttongroup::properties::ID_POSITION); 407 408 // FIXME: is it really necessary to update the opengl canvas here? 409 Canvas *canvas = m_container->canvas (m_handle); 410 411 if (canvas) 412 canvas->redraw (); 413 } 414 415 void updateLayout(void)416 ButtonGroup::updateLayout (void) 417 { 418 uibuttongroup::properties& pp = properties<uibuttongroup> (); 419 QFrame *frame = qWidget<QFrame> (); 420 421 Matrix bb = pp.get_boundingbox (true); 422 int bw = borderWidthFromProperties (pp); 423 424 frame->setFrameRect (QRect (octave::math::round (bb(0)) - bw, 425 octave::math::round (bb(1)) - bw, 426 octave::math::round (bb(2)) + 2*bw, octave::math::round (bb(3)) + 2*bw)); 427 m_container->setGeometry (octave::math::round (bb(0)), 428 octave::math::round (bb(1)), 429 octave::math::round (bb(2)), octave::math::round (bb(3))); 430 431 if (m_blockUpdates) 432 pp.update_boundingbox (); 433 434 if (m_title) 435 { 436 QSize sz = m_title->sizeHint (); 437 int offset = 5; 438 439 if (pp.titleposition_is ("lefttop")) 440 m_title->move (bw+offset, 0); 441 else if (pp.titleposition_is ("righttop")) 442 m_title->move (frame->width () - bw - offset - sz.width (), 0); 443 else if (pp.titleposition_is ("leftbottom")) 444 m_title->move (bw+offset, frame->height () - sz.height ()); 445 else if (pp.titleposition_is ("rightbottom")) 446 m_title->move (frame->width () - bw - offset - sz.width (), 447 frame->height () - sz.height ()); 448 else if (pp.titleposition_is ("centertop")) 449 m_title->move (frame->width () / 2 - sz.width () / 2, 0); 450 else if (pp.titleposition_is ("centerbottom")) 451 m_title->move (frame->width () / 2 - sz.width () / 2, 452 frame->height () - sz.height ()); 453 } 454 } 455 456 void selectNothing(void)457 ButtonGroup::selectNothing (void) 458 { 459 m_hiddenbutton->setChecked (true); 460 } 461 462 463 void addButton(QAbstractButton * btn)464 ButtonGroup::addButton (QAbstractButton *btn) 465 { 466 m_buttongroup->addButton (btn); 467 connect (btn, SIGNAL (toggled (bool)), SLOT (buttonToggled (bool))); 468 } 469 470 void buttonToggled(bool toggled)471 ButtonGroup::buttonToggled (bool toggled) 472 { 473 Q_UNUSED (toggled); 474 if (! m_blockUpdates) 475 { 476 gh_manager& gh_mgr = m_interpreter.get_gh_manager (); 477 478 octave::autolock guard (gh_mgr.graphics_lock ()); 479 480 uibuttongroup::properties& bp = properties<uibuttongroup> (); 481 482 graphics_handle oldValue = bp.get_selectedobject (); 483 484 QAbstractButton *checkedBtn = m_buttongroup->checkedButton (); 485 486 graphics_handle newValue = graphics_handle (); 487 if (checkedBtn != m_hiddenbutton) 488 { 489 Object *checkedObj = Object::fromQObject (checkedBtn); 490 newValue = checkedObj->properties ().get___myhandle__ (); 491 } 492 493 if (oldValue != newValue) 494 emit gh_set_event (m_handle, "selectedobject", 495 newValue.as_octave_value (), false); 496 } 497 } 498 499 void buttonClicked(QAbstractButton * btn)500 ButtonGroup::buttonClicked (QAbstractButton *btn) 501 { 502 Q_UNUSED (btn); 503 504 gh_manager& gh_mgr = m_interpreter.get_gh_manager (); 505 506 octave::autolock guard (gh_mgr.graphics_lock ()); 507 508 uibuttongroup::properties& bp = properties<uibuttongroup> (); 509 510 graphics_handle oldValue = bp.get_selectedobject (); 511 512 QAbstractButton *checkedBtn = m_buttongroup->checkedButton (); 513 Object *checkedObj = Object::fromQObject (checkedBtn); 514 graphics_handle newValue = checkedObj->properties ().get___myhandle__ (); 515 516 if (oldValue != newValue) 517 { 518 octave_scalar_map eventData; 519 eventData.setfield ("OldValue", oldValue.as_octave_value ()); 520 eventData.setfield ("NewValue", newValue.as_octave_value ()); 521 eventData.setfield ("Source", bp.get___myhandle__ ().as_octave_value ()); 522 eventData.setfield ("EventName", "SelectionChanged"); 523 octave_value selectionChangedEventObject (new octave_struct (eventData)); 524 emit gh_callback_event (m_handle, "selectionchangedfcn", 525 selectionChangedEventObject); 526 } 527 } 528 529 }; 530