1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2011-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 <QEvent>
31 #include <QFrame>
32 #include <QLabel>
33 #include <QMouseEvent>
34 #include <QTimer>
35 
36 #include "Canvas.h"
37 #include "Container.h"
38 #include "ContextMenu.h"
39 #include "Panel.h"
40 #include "QtHandlesUtils.h"
41 
42 #include "octave-qobject.h"
43 
44 #include "graphics.h"
45 #include "interpreter.h"
46 
47 namespace QtHandles
48 {
49 
50   static int
frameStyleFromProperties(const uipanel::properties & pp)51   frameStyleFromProperties (const uipanel::properties& pp)
52   {
53     if (pp.bordertype_is ("none"))
54       return QFrame::NoFrame;
55     else if (pp.bordertype_is ("etchedin"))
56       return (QFrame::Box | QFrame::Sunken);
57     else if (pp.bordertype_is ("etchedout"))
58       return (QFrame::Box | QFrame::Raised);
59     else if (pp.bordertype_is ("beveledin"))
60       return (QFrame::Panel | QFrame::Sunken);
61     else if (pp.bordertype_is ("beveledout"))
62       return (QFrame::Panel | QFrame::Raised);
63     else
64       return (QFrame::Panel | QFrame::Plain);
65   }
66 
67   static void
setupPalette(const uipanel::properties & pp,QPalette & p)68   setupPalette (const uipanel::properties& pp, QPalette& p)
69   {
70     p.setColor (QPalette::Window,
71                 Utils::fromRgb (pp.get_backgroundcolor_rgb ()));
72     p.setColor (QPalette::WindowText,
73                 Utils::fromRgb (pp.get_foregroundcolor_rgb ()));
74     p.setColor (QPalette::Light,
75                 Utils::fromRgb (pp.get_highlightcolor_rgb ()));
76     p.setColor (QPalette::Dark,
77                 Utils::fromRgb (pp.get_shadowcolor_rgb ()));
78   }
79 
80   static int
borderWidthFromProperties(const uipanel::properties & pp)81   borderWidthFromProperties (const uipanel::properties& pp)
82   {
83     int bw = 0;
84 
85     if (! pp.bordertype_is ("none"))
86       {
87         bw = octave::math::round (pp.get_borderwidth ());
88         if (pp.bordertype_is ("etchedin") || pp.bordertype_is ("etchedout"))
89           bw *= 2;
90       }
91 
92     return bw;
93   }
94 
95   Panel*
create(octave::base_qobject & oct_qobj,octave::interpreter & interp,const graphics_object & go)96   Panel::create (octave::base_qobject& oct_qobj, octave::interpreter& interp,
97                  const graphics_object& go)
98   {
99     Object *parent = parentObject (interp, go);
100 
101     if (parent)
102       {
103         Container *container = parent->innerContainer ();
104 
105         if (container)
106           return new Panel (oct_qobj, interp, go, new QFrame (container));
107       }
108 
109     return nullptr;
110   }
111 
Panel(octave::base_qobject & oct_qobj,octave::interpreter & interp,const graphics_object & go,QFrame * frame)112   Panel::Panel (octave::base_qobject& oct_qobj, octave::interpreter& interp,
113                 const graphics_object& go, QFrame *frame)
114     : Object (oct_qobj, interp, go, frame), m_container (nullptr),
115       m_title (nullptr), m_blockUpdates (false),
116       m_previous_bbox (Matrix (1, 4, 0))
117   {
118     uipanel::properties& pp = properties<uipanel> ();
119 
120     frame->setObjectName ("UIPanel");
121     frame->setAutoFillBackground (true);
122     Matrix bb = pp.get_boundingbox (false);
123     frame->setGeometry (octave::math::round (bb(0)), octave::math::round (bb(1)),
124                         octave::math::round (bb(2)), octave::math::round (bb(3)));
125     frame->setFrameStyle (frameStyleFromProperties (pp));
126     frame->setLineWidth (octave::math::round (pp.get_borderwidth ()));
127     QPalette pal = frame->palette ();
128     setupPalette (pp, pal);
129     frame->setPalette (pal);
130 
131     m_container = new Container (frame, oct_qobj, interp);
132     m_container->canvas (m_handle);
133 
134     connect (m_container, SIGNAL (interpeter_event (const fcn_callback&)),
135              this, SIGNAL (interpeter_event (const fcn_callback&)));
136 
137     connect (m_container, SIGNAL (interpeter_event (const meth_callback&)),
138              this, SIGNAL (interpeter_event (const meth_callback&)));
139 
140     if (frame->hasMouseTracking ())
141       {
142         for (auto *w : frame->findChildren<QWidget*> ())
143           w->setMouseTracking (true);
144       }
145 
146     QString title = Utils::fromStdString (pp.get_title ());
147     if (! title.isEmpty ())
148       {
149         m_title = new QLabel (title, frame);
150         m_title->setAutoFillBackground (true);
151         m_title->setContentsMargins (4, 0, 4, 0);
152         m_title->setPalette (pal);
153         m_title->setFont (Utils::computeFont<uipanel> (pp, bb(3)));
154       }
155 
156     frame->installEventFilter (this);
157     m_container->installEventFilter (this);
158 
159     graphics_object fig (go.get_ancestor ("figure"));
160     if (! fig.get ("keypressfcn").isempty ())
161       m_container->canvas (m_handle)->addEventMask (Canvas::KeyPress);
162 
163     if (! fig.get ("keyreleasefcn").isempty ())
164       m_container->canvas (m_handle)->addEventMask (Canvas::KeyRelease);
165 
166     if (pp.is_visible ())
167       QTimer::singleShot (0, frame, SLOT (show (void)));
168     else
169       frame->hide ();
170   }
171 
~Panel(void)172   Panel::~Panel (void)
173   { }
174 
175   bool
eventFilter(QObject * watched,QEvent * xevent)176   Panel::eventFilter (QObject *watched, QEvent *xevent)
177   {
178     if (! m_blockUpdates)
179       {
180         gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
181 
182         if (watched == qObject ())
183           {
184             switch (xevent->type ())
185               {
186               case QEvent::Resize:
187                 {
188                   octave::autolock guard (gh_mgr.graphics_lock ());
189 
190                   graphics_object go = object ();
191 
192                   if (go.valid_object ())
193                     {
194                       if (m_title)
195                         {
196                           const uipanel::properties& pp =
197                             Utils::properties<uipanel> (go);
198 
199                           if (pp.fontunits_is ("normalized"))
200                             {
201                               QFrame *frame = qWidget<QFrame> ();
202 
203                               m_title->setFont (Utils::computeFont<uipanel>
204                                                 (pp, frame->height ()));
205                               m_title->resize (m_title->sizeHint ());
206                             }
207                         }
208                       updateLayout ();
209                     }
210                 }
211                 break;
212 
213               case QEvent::MouseButtonPress:
214                 {
215                   QMouseEvent *m = dynamic_cast<QMouseEvent *> (xevent);
216 
217                   if (m->button () == Qt::RightButton)
218                     {
219                       octave::autolock guard (gh_mgr.graphics_lock ());
220 
221                       graphics_object go = object ();
222 
223                       if (go.valid_object ())
224                         ContextMenu::executeAt (m_interpreter,
225                                                 go.get_properties (),
226                                                 m->globalPos ());
227                     }
228                 }
229                 break;
230 
231               default:
232                 break;
233               }
234           }
235         else if (watched == m_container)
236           {
237             switch (xevent->type ())
238               {
239               case QEvent::Resize:
240                 if (qWidget<QWidget> ()->isVisible ())
241                   {
242                     octave::autolock guard (gh_mgr.graphics_lock ());
243 
244                     graphics_object go = object ();
245 
246                     if (go.valid_object ())
247                       go.get_properties ().update_boundingbox ();
248                   }
249                 break;
250 
251               default:
252                 break;
253               }
254           }
255       }
256 
257     return false;
258   }
259 
260   void
update(int pId)261   Panel::update (int pId)
262   {
263     uipanel::properties& pp = properties<uipanel> ();
264     QFrame *frame = qWidget<QFrame> ();
265 
266     m_blockUpdates = true;
267 
268     switch (pId)
269       {
270       case uipanel::properties::ID_POSITION:
271         {
272           Matrix bb = pp.get_boundingbox (false);
273           if (m_previous_bbox(0) != bb(0) || m_previous_bbox(1) != bb(1)
274               || m_previous_bbox(2) != bb(2) || m_previous_bbox(3) != bb(3))
275             {
276               frame->setGeometry (octave::math::round (bb(0)),
277                                   octave::math::round (bb(1)),
278                                   octave::math::round (bb(2)),
279                                   octave::math::round (bb(3)));
280               updateLayout ();
281             }
282           m_previous_bbox = bb;
283         }
284         break;
285 
286       case uipanel::properties::ID_BORDERWIDTH:
287         frame->setLineWidth (octave::math::round (pp.get_borderwidth ()));
288         updateLayout ();
289         break;
290 
291       case uipanel::properties::ID_BACKGROUNDCOLOR:
292       case uipanel::properties::ID_FOREGROUNDCOLOR:
293       case uipanel::properties::ID_HIGHLIGHTCOLOR:
294       case uipanel::properties::ID_SHADOWCOLOR:
295         {
296           QPalette pal = frame->palette ();
297 
298           setupPalette (pp, pal);
299           frame->setPalette (pal);
300           if (m_title)
301             m_title->setPalette (pal);
302         }
303         break;
304 
305       case uipanel::properties::ID_TITLE:
306         {
307           QString title = Utils::fromStdString (pp.get_title ());
308 
309           if (title.isEmpty ())
310             {
311               if (m_title)
312                 delete m_title;
313               m_title = nullptr;
314             }
315           else
316             {
317               if (! m_title)
318                 {
319                   QPalette pal = frame->palette ();
320 
321                   m_title = new QLabel (title, frame);
322                   m_title->setAutoFillBackground (true);
323                   m_title->setContentsMargins (4, 0, 4, 0);
324                   m_title->setPalette (pal);
325                   m_title->setFont (Utils::computeFont<uipanel> (pp));
326                   m_title->show ();
327                 }
328               else
329                 {
330                   m_title->setText (title);
331                   m_title->resize (m_title->sizeHint ());
332                 }
333             }
334           updateLayout ();
335         }
336         break;
337 
338       case uipanel::properties::ID_TITLEPOSITION:
339         updateLayout ();
340         break;
341 
342       case uipanel::properties::ID_BORDERTYPE:
343         frame->setFrameStyle (frameStyleFromProperties (pp));
344         updateLayout ();
345         break;
346 
347       case uipanel::properties::ID_FONTNAME:
348       case uipanel::properties::ID_FONTSIZE:
349       case uipanel::properties::ID_FONTWEIGHT:
350       case uipanel::properties::ID_FONTANGLE:
351         if (m_title)
352           {
353             m_title->setFont (Utils::computeFont<uipanel> (pp));
354             m_title->resize (m_title->sizeHint ());
355             updateLayout ();
356           }
357         break;
358 
359       case uipanel::properties::ID_VISIBLE:
360         frame->setVisible (pp.is_visible ());
361         updateLayout ();
362         break;
363 
364       default:
365         break;
366       }
367 
368     m_blockUpdates = false;
369   }
370 
371   void
redraw(void)372   Panel::redraw (void)
373   {
374     update (uipanel::properties::ID_POSITION);
375 
376     Canvas *canvas = m_container->canvas (m_handle);
377 
378     if (canvas)
379       canvas->redraw ();
380   }
381 
382   void
updateLayout(void)383   Panel::updateLayout (void)
384   {
385     uipanel::properties& pp = properties<uipanel> ();
386     QFrame *frame = qWidget<QFrame> ();
387 
388     Matrix bb = pp.get_boundingbox (true);
389     int bw = borderWidthFromProperties (pp);
390 
391     frame->setFrameRect (QRect (octave::math::round (bb(0)) - bw,
392                                 octave::math::round (bb(1)) - bw,
393                                 octave::math::round (bb(2)) + 2*bw, octave::math::round (bb(3)) + 2*bw));
394     m_container->setGeometry (octave::math::round (bb(0)),
395                               octave::math::round (bb(1)),
396                               octave::math::round (bb(2)), octave::math::round (bb(3)));
397 
398     if (m_blockUpdates)
399       pp.update_boundingbox ();
400 
401     if (m_title)
402       {
403         QSize sz = m_title->sizeHint ();
404         int offset = 5;
405 
406         if (pp.titleposition_is ("lefttop"))
407           m_title->move (bw+offset, 0);
408         else if (pp.titleposition_is ("righttop"))
409           m_title->move (frame->width () - bw - offset - sz.width (), 0);
410         else if (pp.titleposition_is ("leftbottom"))
411           m_title->move (bw+offset, frame->height () - sz.height ());
412         else if (pp.titleposition_is ("rightbottom"))
413           m_title->move (frame->width () - bw - offset - sz.width (),
414                          frame->height () - sz.height ());
415         else if (pp.titleposition_is ("centertop"))
416           m_title->move (frame->width () / 2 - sz.width () / 2, 0);
417         else if (pp.titleposition_is ("centerbottom"))
418           m_title->move (frame->width () / 2 - sz.width () / 2,
419                          frame->height () - sz.height ());
420       }
421   }
422 
423   void
do_connections(const QObject * receiver,const QObject *)424   Panel::do_connections (const QObject *receiver, const QObject* /* emitter */)
425   {
426     Object::do_connections (receiver);
427     Object::do_connections (receiver, m_container->canvas (m_handle));
428   }
429 
430 };
431