freecad-cam/Mod/Draft/draftutils/todo.py
2026-02-01 01:59:24 +01:00

290 lines
11 KiB
Python

# ***************************************************************************
# * (c) 2009, Yorik van Havre <yorik@uncreated.net> *
# * (c) 2019 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides the ToDo static class to run commands with a time delay.
The `ToDo` class is used to delay the commit of commands for later execution.
This is necessary when a GUI command needs to manipulate the 3D view
in such a way that a callback would crash `Coin`.
The `ToDo` class essentially calls `QtCore.QTimer.singleShot`
to execute the instructions stored in internal lists.
"""
## @package todo
# \ingroup draftutils
# \brief Provides the ToDo static class to run commands with a time delay.
import traceback
import sys
import PySide.QtCore as QtCore
import FreeCAD as App
import FreeCADGui as Gui
from draftutils.messages import _msg, _wrn, _err, _log
__title__ = "FreeCAD Draft Workbench, Todo class"
__author__ = "Yorik van Havre <yorik@uncreated.net>"
__url__ = ["https://www.freecad.org"]
_DEBUG = 0
_DEBUG_inner = 0
## \addtogroup draftutils
# @{
class ToDo:
"""A static class that delays execution of functions.
It calls `QtCore.QTimer.singleShot(0, doTasks)`
where `doTasks` is a static method which executes
the commands stored in the list attributes.
Attributes
----------
itinerary: list of tuples
Each tuple is of the form `(name, arg)`.
The `name` is a reference (pointer) to a function,
and `arg` is the corresponding argument that is passed
to that function.
It then tries executing the function with the argument,
if available, or without it, if not available.
::
name(arg)
name()
commitlist: list of tuples
Each tuple is of the form `(name, command_list)`.
The `name` is a string identifier or description of the commands
that will be run, and `command_list` is a list of strings
that indicate the Python instructions that will be executed,
or a reference to a single function that will be executed.
If `command_list` is a list, the program opens a transaction,
then runs all commands in the list in sequence,
and finally commits the transaction.
::
command_list = ["command1", "command2", "..."]
App.activeDocument().openTransaction(name)
Gui.doCommand("command1")
Gui.doCommand("command2")
Gui.doCommand("...")
App.activeDocument().commitTransaction()
If `command_list` is a reference to a function
the function is executed directly.
::
command_list = function
App.activeDocument().openTransaction(name)
function()
App.activeDocument().commitTransaction()
afteritinerary: list of tuples
Each tuple is of the form `(name, arg)`.
This list is used just like `itinerary`.
Lists
-----
The lists contain tuples. Each tuple contains a `name` which is just
a string to identify the operation, and a `command_list` which is
a list of strings, each string an individual Python instruction.
"""
itinerary = []
commitlist = []
afteritinerary = []
@staticmethod
def doTasks():
"""Execute the commands stored in the lists.
The lists are `itinerary`, `commitlist` and `afteritinerary`.
"""
if _DEBUG:
_msg("Debug: doing delayed tasks.\n"
"itinerary: {0}\n"
"commitlist: {1}\n"
"afteritinerary: {2}\n".format(todo.itinerary,
todo.commitlist,
todo.afteritinerary))
try:
for f, arg in ToDo.itinerary:
try:
if _DEBUG_inner:
_msg("Debug: executing.\n"
"function: {}\n".format(f))
if arg or (arg is False):
f(arg)
else:
f()
except Exception:
_log(traceback.format_exc())
_err(traceback.format_exc())
wrn = ("ToDo.doTasks, Unexpected error:\n"
"{0}\n"
"in {1}({2})".format(sys.exc_info()[0], f, arg))
_wrn(wrn)
except ReferenceError:
_wrn("Debug: ToDo.doTasks: "
"queue contains a deleted object, skipping")
ToDo.itinerary = []
if ToDo.commitlist:
commit_list = ToDo.commitlist
ToDo.commitlist = [] # Reset immediately to avoid race condition.
for name, func in commit_list:
if _DEBUG_inner:
_msg("Debug: committing.\n"
"name: {}\n".format(name))
try:
name = str(name)
App.activeDocument().openTransaction(name)
if isinstance(func, list):
for string in func:
Gui.doCommand(string)
else:
func()
App.activeDocument().commitTransaction()
except Exception:
_log(traceback.format_exc())
_err(traceback.format_exc())
wrn = ("ToDo.doTasks, Unexpected error:\n"
"{0}\n"
"in {1}".format(sys.exc_info()[0], func))
_wrn(wrn)
# Restack Draft screen widgets after creation
if hasattr(Gui, "Snapper"):
Gui.Snapper.restack()
for f, arg in ToDo.afteritinerary:
try:
if _DEBUG_inner:
_msg("Debug: executing after.\n"
"function: {}\n".format(f))
if arg:
f(arg)
else:
f()
except Exception:
_log(traceback.format_exc())
_err(traceback.format_exc())
wrn = ("ToDo.doTasks, Unexpected error:\n"
"{0}\n"
"in {1}({2})".format(sys.exc_info()[0], f, arg))
_wrn(wrn)
ToDo.afteritinerary = []
@staticmethod
def delay(f, arg):
"""Add the function and argument to the itinerary list.
Schedule geometry manipulation that would crash Coin if done
in the event callback.
If the `itinerary` list is empty, it will call
`QtCore.QTimer.singleShot(0, ToDo.doTasks)`
to execute the commands in the other lists.
Finally, it will build the tuple `(f, arg)`
and append it to the `itinerary` list.
Parameters
----------
f: function reference
A reference (pointer) to a Python command
which can be executed directly.
::
f()
arg: argument reference
A reference (pointer) to the argument to the `f` function.
::
f(arg)
"""
if _DEBUG:
_msg("Debug: delaying.\n"
"function: {}\n".format(f))
if ToDo.itinerary == []:
QtCore.QTimer.singleShot(0, ToDo.doTasks)
ToDo.itinerary.append((f, arg))
@staticmethod
def delayCommit(cl):
"""Execute the other lists, and add to the commit list.
Schedule geometry manipulation that would crash Coin if done
in the event callback.
First it calls
`QtCore.QTimer.singleShot(0, ToDo.doTasks)`
to execute the commands in all lists.
Then the `cl` list is assigned as the new commit list.
Parameters
----------
cl: list of tuples
Each tuple is of the form `(name, command_list)`.
The `name` is a string identifier or description of the commands
that will be run, and `command_list` is a list of strings
that indicate the Python instructions that will be executed.
See the attributes of the `ToDo` class for more information.
"""
if _DEBUG:
_msg("Debug: delaying commit.\n"
"commitlist: {}\n".format(cl))
QtCore.QTimer.singleShot(0, ToDo.doTasks)
ToDo.commitlist = cl
@staticmethod
def delayAfter(f, arg):
"""Add the function and argument to the afteritinerary list.
Schedule geometry manipulation that would crash Coin if done
in the event callback.
Works the same as `delay`.
If the `afteritinerary` list is empty, it will call
`QtCore.QTimer.singleShot(0, ToDo.doTasks)`
to execute the commands in the other lists.
Finally, it will build the tuple `(f, arg)`
and append it to the `afteritinerary` list.
"""
if _DEBUG:
_msg("Debug: delaying after.\n"
"function: {}\n".format(f))
if ToDo.afteritinerary == []:
QtCore.QTimer.singleShot(0, ToDo.doTasks)
ToDo.afteritinerary.append((f, arg))
# Alias for compatibility with v0.18 and earlier
todo = ToDo
## @}