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