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