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 <cstdint>
31 
32 #include <QApplication>
33 #include <QFontMetrics>
34 #include <QThread>
35 
36 #include "ButtonGroup.h"
37 #include "CheckBoxControl.h"
38 #include "ContextMenu.h"
39 #include "EditControl.h"
40 #include "Figure.h"
41 #include "ListBoxControl.h"
42 #include "Logger.h"
43 #include "Menu.h"
44 #include "Object.h"
45 #include "ObjectProxy.h"
46 #include "Panel.h"
47 #include "PopupMenuControl.h"
48 #include "PushButtonControl.h"
49 #include "PushTool.h"
50 #include "QtHandlesUtils.h"
51 #include "RadioButtonControl.h"
52 #include "SliderControl.h"
53 #include "Table.h"
54 #include "TextControl.h"
55 #include "ToggleButtonControl.h"
56 #include "ToggleTool.h"
57 #include "ToolBar.h"
58 #include "qt-graphics-toolkit.h"
59 
60 #include "octave-qobject.h"
61 
62 #include "event-manager.h"
63 #include "graphics.h"
64 #include "interpreter.h"
65 
66 //#if INTPTR_MAX == INT32_MAX
67 //# define OCTAVE_PTR_TYPE octave_uint32
68 //# define OCTAVE_INTPTR_TYPE uint32_t
69 //# define OCTAVE_PTR_SCALAR uint32_scalar_value
70 //#else
71 # define OCTAVE_PTR_TYPE octave_uint64
72 # define OCTAVE_INTPTR_TYPE uint64_t
73 # define OCTAVE_PTR_SCALAR uint64_scalar_value
74 //#endif
75 
76 namespace QtHandles
77 {
78 
79   static std::string
toolkitObjectProperty(const graphics_object & go)80   toolkitObjectProperty (const graphics_object& go)
81   {
82     if (go.isa ("figure"))
83       return "__plot_stream__";
84     else if (go.isa ("uicontrol")
85              || go.isa ("uipanel")
86              || go.isa ("uibuttongroup")
87              || go.isa ("uimenu")
88              || go.isa ("uicontextmenu")
89              || go.isa ("uitable")
90              || go.isa ("uitoolbar")
91              || go.isa ("uipushtool")
92              || go.isa ("uitoggletool"))
93       return "__object__";
94     else
95       qCritical ("QtHandles::qt_graphics_toolkit: no __object__ property known for object "
96                  "of type %s", go.type ().c_str ());
97 
98     return "";
99   }
100 
qt_graphics_toolkit(octave::interpreter & interp,octave::base_qobject & oct_qobj)101   qt_graphics_toolkit::qt_graphics_toolkit (octave::interpreter& interp,
102                                             octave::base_qobject& oct_qobj)
103     : QObject (), base_graphics_toolkit ("qt"), m_interpreter (interp),
104       m_octave_qobj (oct_qobj)
105   {
106     // Implemented with a signal/slot connection in order to properly
107     // cross from the interpreter thread (where requests to create
108     // graphics object are initiated) to the GUI application thread
109     // (where they are actually created and displayed).
110     // We need to make sure the GUI Object and its proxy are properly
111     // created before the initialize method returns, so we use a
112     // BlockingQueuedConnection. After the signal is emitted, the interpreter
113     // thread is locked until the slot has returned.
114 
115     connect (this, SIGNAL (create_object_signal (double)),
116              this, SLOT (create_object (double)),
117              Qt::BlockingQueuedConnection);
118   }
119 
120   bool
initialize(const graphics_object & go)121   qt_graphics_toolkit::initialize (const graphics_object& go)
122   {
123     if (go.isa ("figure")
124         || (go.isa ("uicontrol") && go.get ("style").string_value () != "frame")
125         || go.isa ("uipanel")
126         || go.isa ("uibuttongroup")
127         || go.isa ("uimenu")
128         || go.isa ("uicontextmenu")
129         || go.isa ("uitable")
130         || go.isa ("uitoolbar")
131         || go.isa ("uipushtool")
132         || go.isa ("uitoggletool"))
133       {
134         // FIXME: We need to unlock the mutex here but we have no way to know
135         // if it was previously locked by this thread, and thus if we should
136         // re-lock it.
137 
138         gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
139 
140         gh_mgr.unlock ();
141 
142         Logger::debug ("qt_graphics_toolkit::initialize %s from thread %08x",
143                        go.type ().c_str (), QThread::currentThreadId ());
144 
145         ObjectProxy *proxy = new ObjectProxy ();
146         graphics_object gObj (go);
147 
148         OCTAVE_PTR_TYPE tmp (reinterpret_cast<OCTAVE_INTPTR_TYPE> (proxy));
149         gObj.get_properties ().set (toolkitObjectProperty (go), tmp);
150 
151         emit create_object_signal (go.get_handle ().value ());
152 
153         return true;
154       }
155 
156     return false;
157   }
158 
159   void
update(const graphics_object & go,int pId)160   qt_graphics_toolkit::update (const graphics_object& go, int pId)
161   {
162     // Rule out obvious properties we want to ignore.
163     if (pId == figure::properties::ID___PLOT_STREAM__
164         || pId == uicontrol::properties::ID___OBJECT__
165         || pId == uipanel::properties::ID___OBJECT__
166         || pId == uibuttongroup::properties::ID___OBJECT__
167         || pId == uimenu::properties::ID___OBJECT__
168         || pId == uicontextmenu::properties::ID___OBJECT__
169         || pId == uitable::properties::ID___OBJECT__
170         || pId == uitoolbar::properties::ID___OBJECT__
171         || pId == uipushtool::properties::ID___OBJECT__
172         || pId == uitoggletool::properties::ID___OBJECT__
173         || pId == base_properties::ID___MODIFIED__)
174       return;
175 
176     Logger::debug ("qt_graphics_toolkit::update %s(%d) from thread %08x",
177                    go.type ().c_str (), pId, QThread::currentThreadId ());
178 
179     ObjectProxy *proxy = toolkitObjectProxy (go);
180 
181     if (proxy)
182       {
183         if (go.isa ("uicontrol")
184             && pId == uicontrol::properties::ID_STYLE)
185           {
186             // Special case: we need to recreate the control widget
187             // associated with the octave graphics_object
188 
189             finalize (go);
190             initialize (go);
191           }
192         else
193           proxy->update (pId);
194       }
195   }
196 
197   void
finalize(const graphics_object & go)198   qt_graphics_toolkit::finalize (const graphics_object& go)
199   {
200     // FIXME: We need to unlock the mutex here but we have no way to know if
201     // if it was previously locked by this thread, and thus if we should
202     // re-lock it.
203 
204     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
205 
206     gh_mgr.unlock ();
207 
208     Logger::debug ("qt_graphics_toolkit::finalize %s from thread %08x",
209                    go.type ().c_str (), QThread::currentThreadId ());
210 
211     ObjectProxy *proxy = toolkitObjectProxy (go);
212 
213     if (proxy)
214       {
215         proxy->finalize ();
216         delete proxy;
217 
218         graphics_object gObj (go);
219 
220         gObj.get_properties ().set (toolkitObjectProperty (go), Matrix ());
221       }
222   }
223 
224   void
redraw_figure(const graphics_object & go) const225   qt_graphics_toolkit::redraw_figure (const graphics_object& go) const
226   {
227     if (go.get_properties ().is_visible ())
228       {
229         ObjectProxy *proxy = toolkitObjectProxy (go);
230 
231         if (proxy)
232           proxy->redraw ();
233       }
234   }
235 
236   void
show_figure(const graphics_object & go) const237   qt_graphics_toolkit::show_figure (const graphics_object& go) const
238   {
239     if (go.get_properties ().is_visible ())
240       {
241         ObjectProxy *proxy = toolkitObjectProxy (go);
242 
243         if (proxy)
244           proxy->show ();
245       }
246   }
247 
248   void
print_figure(const graphics_object & go,const std::string & term,const std::string & file_cmd,const std::string &) const249   qt_graphics_toolkit::print_figure (const graphics_object& go,
250                                      const std::string& term,
251                                      const std::string& file_cmd,
252                                      const std::string& /*debug_file*/) const
253   {
254     ObjectProxy *proxy = toolkitObjectProxy (go);
255 
256     if (proxy)
257       proxy->print (QString::fromStdString (file_cmd),
258                     QString::fromStdString (term));
259   }
260 
261   uint8NDArray
get_pixels(const graphics_object & go) const262   qt_graphics_toolkit::get_pixels (const graphics_object& go) const
263   {
264     uint8NDArray retval;
265 
266     if (go.isa ("figure"))
267       {
268         ObjectProxy *proxy = toolkitObjectProxy (go);
269 
270         if (proxy)
271           retval = proxy->get_pixels ();
272       }
273 
274     return retval;
275   }
276 
277   Matrix
get_text_extent(const graphics_object & go) const278   qt_graphics_toolkit::get_text_extent (const graphics_object& go) const
279   {
280     Matrix ext (1, 4, 0.0);
281 
282     if (go.isa ("uicontrol"))
283       {
284         octave_value str = go.get ("string");
285         if (! str.isempty ())
286           {
287             const uicontrol::properties& up =
288               dynamic_cast<const uicontrol::properties&> (go.get_properties ());
289             Matrix bb = up.get_boundingbox (false);
290             QFont font = Utils::computeFont<uicontrol> (up, bb(3));
291             QFontMetrics fm (font);
292 
293             QString s;
294             QSize sz;
295 
296             if (str.is_string ())
297               {
298                 s = QString::fromStdString (str.string_value ());
299                 sz = fm.size (Qt::TextSingleLine, s);
300                 ext(2) = sz.width ();
301                 ext(3) = sz.height ();
302               }
303             else if (str.iscellstr ())
304               {
305                 string_vector sv = str.string_vector_value ();
306                 double wd = 0.0;
307                 double hg = 0.0;
308                 for (octave_idx_type ii = 0; ii < sv.numel (); ii++)
309                   {
310                     s = QString::fromStdString (sv(ii));
311                     sz = fm.size (Qt::TextSingleLine, s);
312                     wd = std::max (wd, static_cast<double> (sz.width ()));
313                     hg = std::max (hg, static_cast<double> (sz.height ()));
314                   }
315 
316                 ext(2) = wd;
317                 // FIXME: Find a better way to determine the height of e.g.
318                 // listbox uicontrol objects
319                 ext(3) = hg * sv.numel ();
320               }
321           }
322       }
323 
324     return ext;
325   }
326 
327   Object*
toolkitObject(const graphics_object & go)328   qt_graphics_toolkit::toolkitObject (const graphics_object& go)
329   {
330     ObjectProxy *proxy = toolkitObjectProxy (go);
331 
332     if (proxy)
333       return proxy->object ();
334 
335     return nullptr;
336   }
337 
338   ObjectProxy*
toolkitObjectProxy(const graphics_object & go)339   qt_graphics_toolkit::toolkitObjectProxy (const graphics_object& go)
340   {
341     if (go)
342       {
343         octave_value ov = go.get (toolkitObjectProperty (go));
344 
345         if (ov.is_defined () && ! ov.isempty ())
346           {
347             OCTAVE_INTPTR_TYPE ptr = ov.OCTAVE_PTR_SCALAR ().value ();
348 
349             return reinterpret_cast<ObjectProxy *> (ptr);
350           }
351       }
352 
353     return nullptr;
354   }
355 
356   void
interpreter_event(const octave::fcn_callback & fcn)357   qt_graphics_toolkit::interpreter_event (const octave::fcn_callback& fcn)
358   {
359     octave::event_manager& evmgr = m_interpreter.get_event_manager ();
360 
361     evmgr.post_event (fcn);
362   }
363 
364   void
interpreter_event(const octave::meth_callback & meth)365   qt_graphics_toolkit::interpreter_event (const octave::meth_callback& meth)
366   {
367     octave::event_manager& evmgr = m_interpreter.get_event_manager ();
368 
369     evmgr.post_event (meth);
370   }
371 
372   void
create_object(double handle)373   qt_graphics_toolkit::create_object (double handle)
374   {
375     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
376 
377     octave::autolock guard (gh_mgr.graphics_lock ());
378 
379     graphics_object go (gh_mgr.get_object (graphics_handle (handle)));
380 
381     if (! go.valid_object ())
382       {
383         qWarning ("qt_graphics_toolkit::create_object: invalid object for handle %g",
384                   handle);
385         return;
386       }
387 
388     if (go.get_properties ().is_beingdeleted ())
389       {
390         qWarning ("qt_graphics_toolkit::create_object: object is being deleted");
391         return;
392       }
393 
394     ObjectProxy *proxy = qt_graphics_toolkit::toolkitObjectProxy (go);
395 
396     if (! proxy)
397       {
398         qWarning ("qt_graphics_toolkit::create_object: no proxy for handle %g",
399                   handle);
400         return;
401       }
402 
403     Logger::debug ("qt_graphics_toolkit::create_object: "
404                    "create %s from thread %08x",
405                    go.type ().c_str (), QThread::currentThreadId ());
406 
407     Object *obj = nullptr;
408 
409     if (go.isa ("figure"))
410       obj = Figure::create (m_octave_qobj, m_interpreter, go);
411     else if (go.isa ("uicontrol"))
412       {
413         uicontrol::properties& up =
414           Utils::properties<uicontrol> (go);
415 
416         if (up.style_is ("pushbutton"))
417           obj = PushButtonControl::create (m_octave_qobj, m_interpreter, go);
418         else if (up.style_is ("edit"))
419           obj = EditControl::create (m_octave_qobj, m_interpreter, go);
420         else if (up.style_is ("checkbox"))
421           obj = CheckBoxControl::create (m_octave_qobj, m_interpreter, go);
422         else if (up.style_is ("radiobutton"))
423           obj = RadioButtonControl::create (m_octave_qobj, m_interpreter, go);
424         else if (up.style_is ("togglebutton"))
425           obj = ToggleButtonControl::create (m_octave_qobj, m_interpreter, go);
426         else if (up.style_is ("text"))
427           obj = TextControl::create (m_octave_qobj, m_interpreter, go);
428         else if (up.style_is ("popupmenu"))
429           obj = PopupMenuControl::create (m_octave_qobj, m_interpreter, go);
430         else if (up.style_is ("slider"))
431           obj = SliderControl::create (m_octave_qobj, m_interpreter, go);
432         else if (up.style_is ("listbox"))
433           obj = ListBoxControl::create (m_octave_qobj, m_interpreter, go);
434       }
435     else if (go.isa ("uibuttongroup"))
436       obj = ButtonGroup::create (m_octave_qobj, m_interpreter, go);
437     else if (go.isa ("uipanel"))
438       obj = Panel::create (m_octave_qobj, m_interpreter, go);
439     else if (go.isa ("uimenu"))
440       obj = Menu::create (m_octave_qobj, m_interpreter, go);
441     else if (go.isa ("uicontextmenu"))
442       obj = ContextMenu::create (m_octave_qobj, m_interpreter, go);
443     else if (go.isa ("uitable"))
444       obj = Table::create (m_octave_qobj, m_interpreter, go);
445     else if (go.isa ("uitoolbar"))
446       obj = ToolBar::create (m_octave_qobj, m_interpreter, go);
447     else if (go.isa ("uipushtool"))
448       obj = PushTool::create (m_octave_qobj, m_interpreter, go);
449     else if (go.isa ("uitoggletool"))
450       obj = ToggleTool::create (m_octave_qobj, m_interpreter, go);
451     else
452       qWarning ("qt_graphics_toolkit::create_object: unsupported type '%s'",
453                 go.type ().c_str ());
454 
455     if (obj)
456       {
457         proxy->setObject (obj);
458         obj->do_connections (this);
459       }
460   }
461 
gh_callback_event(const graphics_handle & h,const std::string & nm)462   void qt_graphics_toolkit::gh_callback_event (const graphics_handle& h,
463                                                const std::string& nm)
464   {
465     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
466 
467     gh_mgr.post_callback (h, nm);
468   }
469 
gh_callback_event(const graphics_handle & h,const std::string & nm,const octave_value & data)470   void qt_graphics_toolkit::gh_callback_event (const graphics_handle& h,
471                                                const std::string& nm,
472                                                const octave_value& data)
473   {
474     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
475 
476     gh_mgr.post_callback (h, nm, data);
477   }
478 
gh_set_event(const graphics_handle & h,const std::string & nm,const octave_value & value)479   void qt_graphics_toolkit::gh_set_event (const graphics_handle& h,
480                                           const std::string& nm,
481                                           const octave_value& value)
482   {
483     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
484 
485     gh_mgr.post_set (h, nm, value);
486   }
487 
gh_set_event(const graphics_handle & h,const std::string & nm,const octave_value & value,bool notify_toolkit)488   void qt_graphics_toolkit::gh_set_event (const graphics_handle& h,
489                                           const std::string& nm,
490                                           const octave_value& value,
491                                           bool notify_toolkit)
492   {
493     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
494 
495     gh_mgr.post_set (h, nm, value, notify_toolkit);
496   }
497 
gh_set_event(const graphics_handle & h,const std::string & nm,const octave_value & value,bool notify_toolkit,bool redraw_figure)498   void qt_graphics_toolkit::gh_set_event (const graphics_handle& h,
499                                           const std::string& nm,
500                                           const octave_value& value,
501                                           bool notify_toolkit,
502                                           bool redraw_figure)
503   {
504     gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
505 
506     gh_mgr.post_set (h, nm, value, notify_toolkit, redraw_figure);
507   }
508 };
509