Fix 'bl_app_override' wrapping multiple times.
[blender.git] / release / scripts / modules / bl_app_override / __init__.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 """
22 Module to manage overriding various parts of Blender.
23
24 Intended for use with 'app_templates', though it can be used from anywhere.
25 """
26
27
28 # TODO, how to check these aren't from add-ons.
29 # templates might need to un-register while filtering.
30 def class_filter(cls_parent, **kw):
31     whitelist = kw.pop("whitelist", None)
32     blacklist = kw.pop("blacklist", None)
33     kw_items = tuple(kw.items())
34     for cls in cls_parent.__subclasses__():
35         # same as is_registered()
36         if "bl_rna" in cls.__dict__:
37             if blacklist is not None and cls.__name__ in blacklist:
38                 continue
39             if ((whitelist is not None and cls.__name__ is whitelist) or
40                     all((getattr(cls, attr) in expect) for attr, expect in kw_items)):
41                 yield cls
42
43
44 def ui_draw_filter_register(
45     *,
46     ui_ignore_classes=None,
47     ui_ignore_operator=None,
48     ui_ignore_property=None,
49     ui_ignore_menu=None,
50     ui_ignore_label=None
51 ):
52     import bpy
53
54     UILayout = bpy.types.UILayout
55
56     if ui_ignore_classes is None:
57         ui_ignore_classes = (
58             bpy.types.Panel,
59             bpy.types.Menu,
60             bpy.types.Header,
61         )
62
63     class OperatorProperties_Fake:
64         pass
65
66     class UILayout_Fake(bpy.types.UILayout):
67         __slots__ = ()
68
69         def __getattribute__(self, attr):
70             # ensure we always pass down UILayout_Fake instances
71             if attr in {"row", "split", "column", "box", "column_flow"}:
72                 real_func = UILayout.__getattribute__(self, attr)
73
74                 def dummy_func(*args, **kw):
75                     # print("wrapped", attr)
76                     ret = real_func(*args, **kw)
77                     return UILayout_Fake(ret)
78                 return dummy_func
79
80             elif attr in {"operator", "operator_menu_enum", "operator_enum"}:
81                 if ui_ignore_operator is None:
82                     return UILayout.__getattribute__(self, attr)
83
84                 real_func = UILayout.__getattribute__(self, attr)
85
86                 def dummy_func(*args, **kw):
87                     # print("wrapped", attr)
88                     if not ui_ignore_operator(args[0]):
89                         ret = real_func(*args, **kw)
90                     else:
91                         # UILayout.__getattribute__(self, "label")()
92                         # may need to be set
93                         ret = OperatorProperties_Fake()
94                     return ret
95                 return dummy_func
96
97             elif attr in {"prop", "prop_enum"}:
98                 if ui_ignore_property is None:
99                     return UILayout.__getattribute__(self, attr)
100
101                 real_func = UILayout.__getattribute__(self, attr)
102
103                 def dummy_func(*args, **kw):
104                     # print("wrapped", attr)
105                     if not ui_ignore_property(args[0].__class__.__name__, args[1]):
106                         ret = real_func(*args, **kw)
107                     else:
108                         ret = None
109                     return ret
110                 return dummy_func
111
112             elif attr == "menu":
113                 if ui_ignore_menu is None:
114                     return UILayout.__getattribute__(self, attr)
115
116                 real_func = UILayout.__getattribute__(self, attr)
117
118                 def dummy_func(*args, **kw):
119                     # print("wrapped", attr)
120                     if not ui_ignore_menu(args[0]):
121                         ret = real_func(*args, **kw)
122                     else:
123                         ret = None
124                     return ret
125                 return dummy_func
126
127             elif attr == "label":
128                 if ui_ignore_label is None:
129                     return UILayout.__getattribute__(self, attr)
130
131                 real_func = UILayout.__getattribute__(self, attr)
132
133                 def dummy_func(*args, **kw):
134                     # print("wrapped", attr)
135                     if not ui_ignore_label(args[0] if args else kw.get("text", "")):
136                         ret = real_func(*args, **kw)
137                     else:
138                         # ret = real_func()
139                         ret = None
140                     return ret
141                 return dummy_func
142             else:
143                 return UILayout.__getattribute__(self, attr)
144             # print(self, attr)
145
146         def operator(*args, **kw):
147             return super().operator(*args, **kw)
148
149     def draw_override(func_orig, self_real, context):
150         cls_real = self_real.__class__
151         if cls_real is super:
152             # simple, no wrapping
153             return func_orig(self_real, context)
154
155         class Wrapper(cls_real):
156             __slots__ = ()
157             def __getattribute__(self, attr):
158                 if attr == "layout":
159                     return UILayout_Fake(self_real.layout)
160                 else:
161                     cls = super()
162                     try:
163                         return cls.__getattr__(self, attr)
164                     except AttributeError:
165                         # class variable
166                         try:
167                             return getattr(cls, attr)
168                         except AttributeError:
169                             # for preset bl_idname access
170                             return getattr(UILayout(self), attr)
171
172             @property
173             def layout(self):
174                 # print("wrapped")
175                 return self_real.layout
176
177         return func_orig(Wrapper(self_real), context)
178
179     ui_ignore_store = []
180
181     for cls in ui_ignore_classes:
182         for subcls in list(cls.__subclasses__()):
183             if "draw" in subcls.__dict__:  # don't want to get parents draw()
184
185                 def replace_draw():
186                     # function also serves to hold draw_old in a local name-space
187                     draw_orig = subcls.draw
188
189                     def draw(self, context):
190                         return draw_override(draw_orig, self, context)
191                     subcls.draw = draw
192
193                 ui_ignore_store.append((subcls, "draw", subcls.draw))
194
195                 replace_draw()
196
197     return ui_ignore_store
198
199
200 def ui_draw_filter_unregister(ui_ignore_store):
201     for (obj, attr, value) in ui_ignore_store:
202         setattr(obj, attr, value)