Merge with trunk, revision 28528 - 28976.
[blender-staging.git] / release / scripts / modules / rna_prop_ui.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 compliant>
20
21 import bpy
22
23
24 def rna_idprop_ui_get(item, create=True):
25     try:
26         return item['_RNA_UI']
27     except:
28         if create:
29             item['_RNA_UI'] = {}
30             return item['_RNA_UI']
31         else:
32             return None
33
34
35 def rna_idprop_ui_prop_get(item, prop, create=True):
36
37     rna_ui = rna_idprop_ui_get(item, create)
38
39     if rna_ui == None:
40         return None
41
42     try:
43         return rna_ui[prop]
44     except:
45         rna_ui[prop] = {}
46         return rna_ui[prop]
47
48
49 def rna_idprop_ui_prop_clear(item, prop):
50     rna_ui = rna_idprop_ui_get(item, False)
51
52     if rna_ui == None:
53         return
54
55     try:
56         del rna_ui[prop]
57     except:
58         pass
59
60
61 def draw(layout, context, context_member, use_edit=True):
62
63     def assign_props(prop, val, key):
64         prop.path = context_member
65         prop.property = key
66
67         try:
68             prop.value = str(val)
69         except:
70             pass
71
72     rna_item = eval("context." + context_member)
73
74     # poll should really get this...
75     if not rna_item:
76         return
77
78     items = rna_item.items()
79     items.sort()
80
81     if use_edit:
82         row = layout.row()
83         props = row.operator("wm.properties_add", text="Add")
84         props.path = context_member
85         del row
86
87     for key, val in items:
88
89         if key == '_RNA_UI':
90             continue
91
92         row = layout.row()
93         convert_to_pyobject = getattr(val, "convert_to_pyobject", None)
94
95         val_orig = val
96         if convert_to_pyobject:
97             val_draw = val = val.convert_to_pyobject()
98             val_draw = str(val_draw)
99         else:
100             val_draw = val
101
102         box = row.box()
103
104         if use_edit:
105             split = box.split(percentage=0.75)
106             row = split.row()
107         else:
108             row = box.row()
109
110         row.label(text=key)
111
112         # explicit exception for arrays
113         if convert_to_pyobject and not hasattr(val_orig, "len"):
114             row.label(text=val_draw)
115         else:
116             row.prop(rna_item, '["%s"]' % key, text="")
117
118         if use_edit:
119             row = split.row(align=True)
120             prop = row.operator("wm.properties_edit", text="edit")
121             assign_props(prop, val_draw, key)
122
123             prop = row.operator("wm.properties_remove", text="", icon='ZOOMOUT')
124             assign_props(prop, val_draw, key)
125
126
127 class PropertyPanel(bpy.types.Panel):
128     """
129     The subclass should have its own poll function
130     and the variable '_context_path' MUST be set.
131     """
132     bl_label = "Custom Properties"
133     bl_default_closed = True
134
135     def draw(self, context):
136         draw(self.layout, context, self._context_path)
137
138
139 from bpy.props import *
140
141
142 rna_path = StringProperty(name="Property Edit",
143     description="Property path edit", maxlen=1024, default="", options={'HIDDEN'})
144
145 rna_value = StringProperty(name="Property Value",
146     description="Property value edit", maxlen=1024, default="")
147
148 rna_property = StringProperty(name="Property Name",
149     description="Property name edit", maxlen=1024, default="")
150
151 rna_min = FloatProperty(name="Min", default=0.0, precision=3)
152 rna_max = FloatProperty(name="Max", default=1.0, precision=3)
153
154
155 class WM_OT_properties_edit(bpy.types.Operator):
156     '''Internal use (edit a property path)'''
157     bl_idname = "wm.properties_edit"
158     bl_label = "Edit Property"
159
160     path = rna_path
161     property = rna_property
162     value = rna_value
163     min = rna_min
164     max = rna_max
165     description = StringProperty(name="Tip", default="")
166
167     def execute(self, context):
168         path = self.properties.path
169         value = self.properties.value
170         prop = self.properties.property
171         prop_old = self._last_prop[0]
172
173         try:
174             value_eval = eval(value)
175         except:
176             value_eval = value
177
178         # First remove
179         item = eval("context.%s" % path)
180
181         rna_idprop_ui_prop_clear(item, prop_old)
182         exec_str = "del item['%s']" % prop_old
183         # print(exec_str)
184         exec(exec_str)
185
186
187         # Reassign
188         exec_str = "item['%s'] = %s" % (prop, repr(value_eval))
189         # print(exec_str)
190         exec(exec_str)
191         self._last_prop[:] = [prop]
192
193         prop_type = type(item[prop])
194
195         prop_ui = rna_idprop_ui_prop_get(item, prop)
196
197         if prop_type in (float, int):
198
199             prop_ui['soft_min'] = prop_ui['min'] = prop_type(self.properties.min)
200             prop_ui['soft_max'] = prop_ui['max'] = prop_type(self.properties.max)
201
202         prop_ui['description'] = self.properties.description
203
204         return {'FINISHED'}
205
206     def invoke(self, context, event):
207
208         self._last_prop = [self.properties.property]
209
210         item = eval("context.%s" % self.properties.path)
211
212         # setup defaults
213         prop_ui = rna_idprop_ui_prop_get(item, self.properties.property, False) # dont create
214         if prop_ui:
215             self.properties.min = prop_ui.get("min", -1000000000)
216             self.properties.max = prop_ui.get("max", 1000000000)
217             self.properties.description = prop_ui.get("description", "")
218
219         wm = context.manager
220         # This crashes, TODO - fix
221         #return wm.invoke_props_popup(self, event)
222
223         wm.invoke_props_popup(self, event)
224         return {'RUNNING_MODAL'}
225
226
227 class WM_OT_properties_add(bpy.types.Operator):
228     '''Internal use (edit a property path)'''
229     bl_idname = "wm.properties_add"
230     bl_label = "Add Property"
231
232     path = rna_path
233
234     def execute(self, context):
235         item = eval("context.%s" % self.properties.path)
236
237         def unique_name(names):
238             prop = 'prop'
239             prop_new = prop
240             i = 1
241             while prop_new in names:
242                 prop_new = prop + str(i)
243                 i += 1
244
245             return prop_new
246
247         property = unique_name(item.keys())
248
249         item[property] = 1.0
250         return {'FINISHED'}
251
252
253 class WM_OT_properties_remove(bpy.types.Operator):
254     '''Internal use (edit a property path)'''
255     bl_idname = "wm.properties_remove"
256     bl_label = "Remove Property"
257
258     path = rna_path
259     property = rna_property
260
261     def execute(self, context):
262         item = eval("context.%s" % self.properties.path)
263         del item[self.properties.property]
264         return {'FINISHED'}