PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/Qitom/python/pythonUiTimer.cpp

https://bitbucket.org/itom/itom
C++ | 557 lines | 455 code | 59 blank | 43 comment | 52 complexity | fd48035a977c050f4859821427a50422 MD5 | raw file
  1. /* ********************************************************************
  2. itom software
  3. URL: http://www.uni-stuttgart.de/ito
  4. Copyright (C) 2020, Institut fuer Technische Optik (ITO),
  5. Universitaet Stuttgart, Germany
  6. This file is part of itom.
  7. itom is free software; you can redistribute it and/or modify it
  8. under the terms of the GNU Library General Public Licence as published by
  9. the Free Software Foundation; either version 2 of the Licence, or (at
  10. your option) any later version.
  11. itom is distributed in the hope that it will be useful, but
  12. WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
  14. General Public Licence for more details.
  15. You should have received a copy of the GNU Library General Public License
  16. along with itom. If not, see <http://www.gnu.org/licenses/>.
  17. *********************************************************************** */
  18. #include "pythonUiTimer.h"
  19. #include "structmember.h"
  20. #include "../global.h"
  21. #include "../organizer/uiOrganizer.h"
  22. #include "pythonEngineInc.h"
  23. #include "AppManagement.h"
  24. #include <iostream>
  25. #include <qvector.h>
  26. #include <qtimer.h>
  27. namespace ito
  28. {
  29. //-------------------------------------------------------------------------------------
  30. TimerCallback::TimerCallback() :
  31. m_function(nullptr),
  32. m_boundedInstance(nullptr),
  33. m_callableType(CallableType::Callable_Invalid),
  34. m_callbackArgs(nullptr)
  35. {
  36. }
  37. //-------------------------------------------------------------------------------------
  38. TimerCallback::~TimerCallback()
  39. {
  40. Py_XDECREF(m_function);
  41. Py_XDECREF(m_boundedInstance);
  42. Py_XDECREF(m_callbackArgs);
  43. }
  44. //-------------------------------------------------------------------------------------
  45. void TimerCallback::timeout()
  46. {
  47. PythonEngine *pyEngine = qobject_cast<PythonEngine*>(AppManagement::getPythonEngine());
  48. if (Py_IsInitialized() == 0)
  49. {
  50. qDebug("python is not available any more");
  51. return;
  52. }
  53. if (m_function == nullptr)
  54. {
  55. qDebug("invalid callable slot.");
  56. return;
  57. }
  58. PyGILState_STATE state = PyGILState_Ensure();
  59. bool debug = false;
  60. if (pyEngine)
  61. {
  62. debug = pyEngine->execInternalCodeByDebugger();
  63. }
  64. if (m_callableType == CallableType::Callable_Function)
  65. {
  66. if (debug)
  67. {
  68. pyEngine->pythonDebugFunction(m_function, m_callbackArgs, true);
  69. }
  70. else
  71. {
  72. pyEngine->pythonRunFunction(m_function, m_callbackArgs, true);
  73. }
  74. }
  75. else if (m_callableType == CallableType::Callable_Method)
  76. {
  77. PyObject *func = PyWeakref_GetObject(m_function);
  78. PyObject *inst = PyWeakref_GetObject(m_boundedInstance);
  79. if (func == Py_None || inst == Py_None)
  80. {
  81. PyErr_SetString(PyExc_RuntimeError, "The python slot method is not longer available");
  82. PyErr_PrintEx(0);
  83. PyErr_Clear();
  84. }
  85. else
  86. {
  87. PyObject *method = PyMethod_New(func, inst); //new ref
  88. if (debug)
  89. {
  90. pyEngine->pythonDebugFunction(method, m_callbackArgs, true);
  91. }
  92. else
  93. {
  94. pyEngine->pythonRunFunction(method, m_callbackArgs, true);
  95. }
  96. Py_XDECREF(method);
  97. }
  98. }
  99. else if (m_callableType == CallableType::Callable_CFunction)
  100. {
  101. PyCFunctionObject* cfunc = (PyCFunctionObject*)m_function;
  102. PyObject *method = PyCFunction_NewEx(cfunc->m_ml, cfunc->m_self, NULL);
  103. if (method)
  104. {
  105. if (debug)
  106. {
  107. pyEngine->pythonDebugFunction(method, m_callbackArgs, true);
  108. }
  109. else
  110. {
  111. pyEngine->pythonRunFunction(method, m_callbackArgs, true);
  112. }
  113. }
  114. Py_XDECREF(method);
  115. }
  116. else
  117. {
  118. qDebug("invalid m_callableType.");
  119. }
  120. PyGILState_Release(state);
  121. }
  122. //-------------------------------------------------------------------------------------
  123. void PythonTimer::PyTimer_dealloc(PyTimer* self)
  124. {
  125. if (self->timer)
  126. {
  127. self->timer->stop();
  128. }
  129. self->callbackFunc.clear();
  130. self->timer.clear();
  131. }
  132. //-------------------------------------------------------------------------------------
  133. PyObject* PythonTimer::PyTimer_new(PyTypeObject *type, PyObject * /*args*/, PyObject * /*kwds*/)
  134. {
  135. PyTimer* self = (PyTimer *)type->tp_alloc(type, 0);
  136. if (self != nullptr)
  137. {
  138. self->timer.clear();
  139. self->callbackFunc.clear();
  140. }
  141. return (PyObject *)self;
  142. }
  143. //-------------------------------------------------------------------------------------
  144. PyDoc_STRVAR(PyTimerInit_doc,"timer(interval, callbackFunc, argTuple = (), singleShot = False, name = \"\", startAfterInit = True) -> timer \n\
  145. \n\
  146. Creates a new timer object for (continously) triggering a callback function \n\
  147. \n\
  148. Creates a timer object that (continuously) calls a python callback function or method. \n\
  149. The timer is active right from the beginning, hence, after creating this object. \n\
  150. If ``singleShot`` is ``True``, the callback function is triggered once after the \n\
  151. interval is passed (denoted as timeout). Else, the callback is continuously triggered \n\
  152. with the given interval. \n\
  153. \n\
  154. Please note, that the timer objects may time out later than expected if Python is \n\
  155. currently busy or the operating system is unable to provide the requested accuracy. \n\
  156. In such a case of timeout overrun, the callback function is only triggered once, \n\
  157. even if multiple timeouts have expired, and then will resume the original interval. \n\
  158. \n\
  159. An active timer can be stopped by the :meth:`stop` method, or if this object is \n\
  160. deleted. Furthermore, itom provides the :ref:`gui-timermanager` dialog, where \n\
  161. all or selected timers can be started or stopped. \n\
  162. \n\
  163. **New in itom 4.1**: Added optional ``startAfterInit`` argument. \n\
  164. \n\
  165. Parameters \n\
  166. ----------- \n\
  167. interval : int \n\
  168. Time out interval in ms. \n\
  169. callbackFunc : callable \n\
  170. Python method (bounded) or function (unbounded) that should be called whenever \n\
  171. the timer event raises. \n\
  172. argTuple : tuple, optional \n\
  173. Tuple of parameters passed as arguments to the callback function. \n\
  174. singleShot : bool, optional \n\
  175. Defines if this timer only fires one time after its start (``True``) or \n\
  176. continuously (``False``, default). \n\
  177. name : str, optional \n\
  178. Is the optional name of this timer. This name is displayed in the timer \n\
  179. manager dialog (instead of the timer ID, if no name is given. \n\
  180. startAfterInit : bool, optional \n\
  181. If this optional boolean is set to False the timer will not start after initialization. \n\
  182. The timer can be started later by using timer.start(). \n\
  183. \n\
  184. Examples \n\
  185. -------- \n\
  186. >>> import time \n\
  187. ... \n\
  188. ... def callbackFunc(startTime: float, a: int): \n\
  189. ... print(\"%.2f sec elapsed: %i\" % (time.time() - startTime, a)) \n\
  190. ... \n\
  191. ... myTimer = timer(1000, callbackFunc, argTuple = (time.time(), 25)) \n\
  192. \n\
  193. 1.00 sec elapsed: 25 \n\
  194. 2.01 sec elapsed : 25 \n\
  195. 3.01 sec elapsed : 25 \n\
  196. 4.01 sec elapsed : 25");
  197. int PythonTimer::PyTimer_init(PyTimer *self, PyObject *args, PyObject *kwds)
  198. {
  199. const char *kwlist[] = {"interval", "callbackFunc", "argTuple", "singleShot", "name", "startAfterInit", nullptr };
  200. if(args == nullptr || PyTuple_Size(args) == 0) //empty constructor
  201. {
  202. return 0;
  203. }
  204. PyObject *callable = nullptr;
  205. self->callbackFunc = QSharedPointer<TimerCallback>(new TimerCallback());
  206. int timeOut = -1;
  207. unsigned char singleShot = 0;
  208. const char* name = nullptr;
  209. unsigned char startAfterInit = 1;
  210. if(!PyArg_ParseTupleAndKeywords(args, kwds, "iO|O!bsb", const_cast<char**>(kwlist),
  211. &timeOut,
  212. &callable,
  213. &PyTuple_Type, &self->callbackFunc->m_callbackArgs,
  214. &singleShot,
  215. &name,
  216. &startAfterInit))
  217. {
  218. return -1;
  219. }
  220. if (timeOut < 0)
  221. {
  222. PyErr_SetString(PyExc_TypeError, "minimum timeout is 0 ms (immediate fire).");
  223. return -1;
  224. }
  225. if (self->callbackFunc->m_callbackArgs)
  226. {
  227. Py_INCREF(self->callbackFunc->m_callbackArgs);
  228. }
  229. else
  230. {
  231. // if the callback function of the timeout event is debugged,
  232. // it must not get a NULL object but at least an empty tuple!
  233. self->callbackFunc->m_callbackArgs = PyTuple_New(0);
  234. }
  235. PyObject *temp = nullptr;
  236. if (PyMethod_Check(callable))
  237. {
  238. self->callbackFunc->m_callableType = TimerCallback::CallableType::Callable_Method;
  239. PyObject *temp = PyMethod_Self(callable); //borrowed
  240. self->callbackFunc->m_boundedInstance = PyWeakref_NewRef(temp, nullptr); //new ref (weak reference used to avoid cyclic garbage collection)
  241. temp = PyMethod_Function(callable); //borrowed
  242. self->callbackFunc->m_function = PyWeakref_NewRef(temp, nullptr); //new ref
  243. }
  244. else if (PyCFunction_Check(callable))
  245. {
  246. self->callbackFunc->m_callableType = TimerCallback::CallableType::Callable_CFunction;
  247. Py_INCREF(callable);
  248. self->callbackFunc->m_function = callable; //new ref
  249. }
  250. else if (PyCallable_Check(callable))
  251. {
  252. // any other callable, especially PyFunction, but also functools.partial etc.
  253. self->callbackFunc->m_callableType = TimerCallback::CallableType::Callable_Function;
  254. Py_INCREF(callable);
  255. self->callbackFunc->m_function = callable; //new ref
  256. }
  257. else
  258. {
  259. PyErr_SetString(PyExc_TypeError, "given method reference is not callable.");
  260. return -1;
  261. }
  262. self->timer = QSharedPointer<QTimer>(new QTimer());
  263. self->timer->setInterval(timeOut);
  264. QMetaObject::Connection conn = QObject::connect(
  265. self->timer.data(), &QTimer::timeout,
  266. self->callbackFunc.data(), &TimerCallback::timeout);
  267. if (!conn)
  268. {
  269. PyErr_SetString(PyExc_TypeError, "error connecting timeout signal/slot");
  270. return -1;
  271. }
  272. self->timer->setSingleShot(singleShot > 0);
  273. self->timer->start();
  274. if (self->timer->timerId() < 0 )
  275. {
  276. if (PyErr_WarnEx(
  277. PyExc_RuntimeWarning,
  278. "timer object could not be created (e.g. negative interval, timer can only "
  279. "be used in threads started with QThread or timers cannot be started "
  280. "from another thread)",
  281. 1) == -1
  282. )
  283. {
  284. // exception is raised instead of warning (depending on user defined warning levels)
  285. return -1;
  286. }
  287. }
  288. else
  289. {
  290. //send timer to timer dialog of main window
  291. UiOrganizer *uiOrg = (UiOrganizer*)AppManagement::getUiOrganizer();
  292. QWeakPointer<QTimer> qTimerPtr = self->timer.toWeakRef();
  293. QString nameStr;
  294. if (name != nullptr)
  295. {
  296. nameStr = QString(name);
  297. }
  298. QMetaObject::invokeMethod(
  299. uiOrg,
  300. "registerActiveTimer",
  301. Q_ARG(QWeakPointer<QTimer>, qTimerPtr),
  302. Q_ARG(QString, nameStr)
  303. );
  304. }
  305. if (!startAfterInit)
  306. {
  307. self->timer->stop();
  308. }
  309. return 0;
  310. }
  311. //-------------------------------------------------------------------------------------
  312. PyObject* PythonTimer::PyTimer_repr(PyTimer *self)
  313. {
  314. PyObject *result = nullptr;
  315. if(self->timer == 0)
  316. {
  317. result = PyUnicode_FromFormat("timer(empty)");
  318. }
  319. else
  320. {
  321. if (self->timer->isSingleShot())
  322. {
  323. result = PyUnicode_FromFormat("timer(interval %i ms, single shot)", self->timer->interval());
  324. }
  325. else
  326. {
  327. result = PyUnicode_FromFormat("timer(interval %i ms)", self->timer->interval());
  328. }
  329. }
  330. return result;
  331. }
  332. //-------------------------------------------------------------------------------------
  333. PyDoc_STRVAR(PyTimerStart_doc,"start() \n\
  334. \n\
  335. Starts the timer. \n\
  336. \n\
  337. This method starts or restarts the timer with its timeout interval. \n\
  338. If the timer is already running, it will be stopped and restarted. \n\
  339. \n\
  340. See Also \n\
  341. --------- \n\
  342. isActive, stop");
  343. PyObject* PythonTimer::PyTimer_start(PyTimer *self)
  344. {
  345. if (self->timer)
  346. {
  347. self->timer->start();
  348. }
  349. Py_RETURN_NONE;
  350. }
  351. //-------------------------------------------------------------------------------------
  352. PyDoc_STRVAR(PyTimerStop_doc, "stop() \n\
  353. \n\
  354. Stops the timer. \n\
  355. \n\
  356. This method stop the timer (if currently active). \n\
  357. \n\
  358. See Also \n\
  359. --------- \n\
  360. isActive, start");
  361. PyObject* PythonTimer::PyTimer_stop(PyTimer *self)
  362. {
  363. if (self->timer)
  364. {
  365. self->timer->stop();
  366. }
  367. Py_RETURN_NONE;
  368. }
  369. //-------------------------------------------------------------------------------------
  370. PyDoc_STRVAR(PyTimerIsActive_doc,"isActive() -> bool \n\
  371. \n\
  372. Indicates if the timer is currently active. \n\
  373. \n\
  374. Returns \n\
  375. ------- \n\
  376. bool \n\
  377. ``True`` if the timer is running, otherwise ``False``.");
  378. PyObject* PythonTimer::PyTimer_isActive(PyTimer *self)
  379. {
  380. if (self->timer)
  381. {
  382. if(self->timer->isActive())
  383. {
  384. Py_RETURN_TRUE;
  385. }
  386. else
  387. {
  388. Py_RETURN_FALSE;
  389. }
  390. }
  391. else
  392. {
  393. PyErr_SetString(PyExc_RuntimeError, "timer is not available.");
  394. return nullptr;
  395. }
  396. }
  397. //-------------------------------------------------------------------------------------
  398. PyDoc_STRVAR(PyTimerSetInterval_doc,"setInterval(interval) \n\
  399. \n\
  400. Sets the timer interval in ms. \n\
  401. \n\
  402. This method sets the timeout interval in milliseconds. If the timer is started, \n\
  403. the callback function is tried to be continously triggered whenever the interval \n\
  404. expired. \n\
  405. \n\
  406. Parameters \n\
  407. ----------- \n\
  408. interval : int \n\
  409. Timeout interval in milliseconds. \n\
  410. \n\
  411. Notes \n\
  412. ------ \n\
  413. If Python is currently busy, a timer event can also be triggered at a later time, \n\
  414. if the same trigger event is not already in the execution queue.");
  415. PyObject* PythonTimer::PyTimer_setInterval(PyTimer *self, PyObject *args)
  416. {
  417. int timeout;
  418. if(!PyArg_ParseTuple(args, "i", &timeout))
  419. {
  420. return nullptr;
  421. }
  422. if (self->timer)
  423. {
  424. self->timer->setInterval(timeout);
  425. }
  426. Py_RETURN_NONE;
  427. }
  428. //-------------------------------------------------------------------------------------
  429. PyMethodDef PythonTimer::PyTimer_methods[] = {
  430. {"start", (PyCFunction)PyTimer_start, METH_NOARGS, PyTimerStart_doc},
  431. {"stop", (PyCFunction)PyTimer_stop, METH_NOARGS, PyTimerStop_doc},
  432. {"isActive", (PyCFunction)PyTimer_isActive, METH_NOARGS, PyTimerIsActive_doc},
  433. {"setInterval", (PyCFunction)PyTimer_setInterval, METH_VARARGS, PyTimerSetInterval_doc},
  434. {nullptr} /* Sentinel */
  435. };
  436. //-------------------------------------------------------------------------------------
  437. PyMemberDef PythonTimer::PyTimer_members[] = {
  438. {nullptr} /* Sentinel */
  439. };
  440. //-------------------------------------------------------------------------------------
  441. PyModuleDef PythonTimer::PyTimerModule = {
  442. PyModuleDef_HEAD_INIT,
  443. "timer",
  444. "timer for callback function",
  445. -1,
  446. nullptr, nullptr, nullptr, nullptr, nullptr
  447. };
  448. //-------------------------------------------------------------------------------------
  449. PyGetSetDef PythonTimer::PyTimer_getseters[] = {
  450. {nullptr} /* Sentinel */
  451. };
  452. //-------------------------------------------------------------------------------------
  453. PyTypeObject PythonTimer::PyTimerType = {
  454. PyVarObject_HEAD_INIT(nullptr, 0)
  455. "itom.timer", /* tp_name */
  456. sizeof(PyTimer), /* tp_basicsize */
  457. 0, /* tp_itemsize */
  458. (destructor)PyTimer_dealloc, /* tp_dealloc */
  459. 0, /* tp_print */
  460. 0, /* tp_getattr */
  461. 0, /* tp_setattr */
  462. 0, /* tp_reserved */
  463. (reprfunc)PyTimer_repr, /* tp_repr */
  464. 0, /* tp_as_number */
  465. 0, /* tp_as_sequence */
  466. 0, /* tp_as_mapping */
  467. 0, /* tp_hash */
  468. 0, /* tp_call */
  469. 0, /* tp_str */
  470. 0, /* tp_getattro */
  471. 0, /* tp_setattro */
  472. 0, /* tp_as_buffer */
  473. Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
  474. PyTimerInit_doc /*"dataObject objects"*/, /* tp_doc */
  475. 0, /* tp_traverse */
  476. 0, /* tp_clear */
  477. 0, /* tp_richcompare */
  478. 0, /* tp_weaklistoffset */
  479. 0, /* tp_iter */
  480. 0, /* tp_iternext */
  481. PyTimer_methods, /* tp_methods */
  482. PyTimer_members, /* tp_members */
  483. PyTimer_getseters, /* tp_getset */
  484. 0, /* tp_base */
  485. 0, /* tp_dict */
  486. 0, /* tp_descr_get */
  487. 0, /* tp_descr_set */
  488. 0, /* tp_dictoffset */
  489. (initproc)PyTimer_init, /* tp_init */
  490. 0, /* tp_alloc */
  491. PyTimer_new /*PyType_GenericNew*/ /*PythonStream_new,*/ /* tp_new */
  492. };
  493. } //end namespace ito
  494. //-------------------------------------------------------------------------------------