pep8 edits
[blender-staging.git] / release / scripts / startup / bl_operators / wm.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 from bpy.types import Menu, Operator
23 from bpy.props import (StringProperty,
24                        BoolProperty,
25                        IntProperty,
26                        FloatProperty,
27                        EnumProperty,
28                        )
29
30 from rna_prop_ui import rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear
31
32 import subprocess
33 import os
34
35
36 class MESH_OT_delete_edgeloop(Operator):
37     '''Delete an edge loop by merging the faces on each side to a single face loop'''
38     bl_idname = "mesh.delete_edgeloop"
39     bl_label = "Delete Edge Loop"
40
41     def execute(self, context):
42         if 'FINISHED' in bpy.ops.transform.edge_slide(value=1.0):
43             bpy.ops.mesh.select_more()
44             bpy.ops.mesh.remove_doubles()
45             return {'FINISHED'}
46
47         return {'CANCELLED'}
48
49 rna_path_prop = StringProperty(
50         name="Context Attributes",
51         description="rna context string",
52         maxlen=1024,
53         )
54
55 rna_reverse_prop = BoolProperty(
56         name="Reverse",
57         description="Cycle backwards",
58         default=False,
59         )
60
61 rna_relative_prop = BoolProperty(
62         name="Relative",
63         description="Apply relative to the current value (delta)",
64         default=False,
65         )
66
67
68 def context_path_validate(context, data_path):
69     try:
70         value = eval("context.%s" % data_path) if data_path else Ellipsis
71     except AttributeError as e:
72         if str(e).startswith("'NoneType'"):
73             # One of the items in the rna path is None, just ignore this
74             value = Ellipsis
75         else:
76             # We have a real error in the rna path, don't ignore that
77             raise
78
79     return value
80
81
82 def operator_value_is_undo(value):
83     if value in {None, Ellipsis}:
84         return False
85
86     # typical properties or objects
87     id_data = getattr(value, "id_data", Ellipsis)
88
89     if id_data is None:
90         return False
91     elif id_data is Ellipsis:
92         # handle mathutils types
93         id_data = getattr(getattr(value, "owner", None), "id_data", None)
94
95         if id_data is None:
96             return False
97
98     # return True if its a non window ID type
99     return (isinstance(id_data, bpy.types.ID) and
100             (not isinstance(id_data, (bpy.types.WindowManager,
101                                       bpy.types.Screen,
102                                       bpy.types.Scene,
103                                       bpy.types.Brush,
104                                       ))))
105
106
107 def operator_path_is_undo(context, data_path):
108     # note that if we have data paths that use strings this could fail
109     # luckily we don't do this!
110     #
111     # When we cant find the data owner assume no undo is needed.
112     data_path_head = data_path.rpartition(".")[0]
113
114     if not data_path_head:
115         return False
116
117     value = context_path_validate(context, data_path_head)
118
119     return operator_value_is_undo(value)
120
121
122 def operator_path_undo_return(context, data_path):
123     return {'FINISHED'} if operator_path_is_undo(context, data_path) else {'CANCELLED'}
124
125
126 def operator_value_undo_return(value):
127     return {'FINISHED'} if operator_value_is_undo(value) else {'CANCELLED'}
128
129
130 def execute_context_assign(self, context):
131     data_path = self.data_path
132     if context_path_validate(context, data_path) is Ellipsis:
133         return {'PASS_THROUGH'}
134
135     if getattr(self, "relative", False):
136         exec("context.%s += self.value" % data_path)
137     else:
138         exec("context.%s = self.value" % data_path)
139
140     return operator_path_undo_return(context, data_path)
141
142
143 class BRUSH_OT_active_index_set(Operator):
144     '''Set active sculpt/paint brush from it's number'''
145     bl_idname = "brush.active_index_set"
146     bl_label = "Set Brush Number"
147
148     mode = StringProperty(
149             name="mode",
150             description="Paint mode to set brush for",
151             maxlen=1024,
152             )
153     index = IntProperty(
154             name="number",
155             description="Brush number",
156             )
157
158     _attr_dict = {"sculpt": "use_paint_sculpt",
159                   "vertex_paint": "use_paint_vertex",
160                   "weight_paint": "use_paint_weight",
161                   "image_paint": "use_paint_image",
162                   }
163
164     def execute(self, context):
165         attr = self._attr_dict.get(self.mode)
166         if attr is None:
167             return {'CANCELLED'}
168
169         for i, brush in enumerate((cur for cur in bpy.data.brushes if getattr(cur, attr))):
170             if i == self.index:
171                 getattr(context.tool_settings, self.mode).brush = brush
172                 return {'FINISHED'}
173
174         return {'CANCELLED'}
175
176
177 class WM_OT_context_set_boolean(Operator):
178     '''Set a context value'''
179     bl_idname = "wm.context_set_boolean"
180     bl_label = "Context Set Boolean"
181     bl_options = {'UNDO', 'INTERNAL'}
182
183     data_path = rna_path_prop
184     value = BoolProperty(
185             name="Value",
186             description="Assignment value",
187             default=True,
188             )
189
190     execute = execute_context_assign
191
192
193 class WM_OT_context_set_int(Operator):  # same as enum
194     '''Set a context value'''
195     bl_idname = "wm.context_set_int"
196     bl_label = "Context Set"
197     bl_options = {'UNDO', 'INTERNAL'}
198
199     data_path = rna_path_prop
200     value = IntProperty(
201             name="Value",
202             description="Assign value",
203             default=0,
204             )
205     relative = rna_relative_prop
206
207     execute = execute_context_assign
208
209
210 class WM_OT_context_scale_int(Operator):
211     '''Scale an int context value'''
212     bl_idname = "wm.context_scale_int"
213     bl_label = "Context Set"
214     bl_options = {'UNDO', 'INTERNAL'}
215
216     data_path = rna_path_prop
217     value = FloatProperty(
218             name="Value",
219             description="Assign value",
220             default=1.0,
221             )
222     always_step = BoolProperty(
223             name="Always Step",
224             description="Always adjust the value by a minimum of 1 when 'value' is not 1.0",
225             default=True,
226             )
227
228     def execute(self, context):
229         data_path = self.data_path
230         if context_path_validate(context, data_path) is Ellipsis:
231             return {'PASS_THROUGH'}
232
233         value = self.value
234
235         if value == 1.0:  # nothing to do
236             return {'CANCELLED'}
237
238         if getattr(self, "always_step", False):
239             if value > 1.0:
240                 add = "1"
241                 func = "max"
242             else:
243                 add = "-1"
244                 func = "min"
245             exec("context.%s = %s(round(context.%s * value), context.%s + %s)" %
246                  (data_path, func, data_path, data_path, add))
247         else:
248             exec("context.%s *= value" % data_path)
249
250         return operator_path_undo_return(context, data_path)
251
252
253 class WM_OT_context_set_float(Operator):  # same as enum
254     '''Set a context value'''
255     bl_idname = "wm.context_set_float"
256     bl_label = "Context Set Float"
257     bl_options = {'UNDO', 'INTERNAL'}
258
259     data_path = rna_path_prop
260     value = FloatProperty(
261             name="Value",
262             description="Assignment value",
263             default=0.0,
264             )
265     relative = rna_relative_prop
266
267     execute = execute_context_assign
268
269
270 class WM_OT_context_set_string(Operator):  # same as enum
271     '''Set a context value'''
272     bl_idname = "wm.context_set_string"
273     bl_label = "Context Set String"
274     bl_options = {'UNDO', 'INTERNAL'}
275
276     data_path = rna_path_prop
277     value = StringProperty(
278             name="Value",
279             description="Assign value",
280             maxlen=1024,
281             )
282
283     execute = execute_context_assign
284
285
286 class WM_OT_context_set_enum(Operator):
287     '''Set a context value'''
288     bl_idname = "wm.context_set_enum"
289     bl_label = "Context Set Enum"
290     bl_options = {'UNDO', 'INTERNAL'}
291
292     data_path = rna_path_prop
293     value = StringProperty(
294             name="Value",
295             description="Assignment value (as a string)",
296             maxlen=1024,
297             )
298
299     execute = execute_context_assign
300
301
302 class WM_OT_context_set_value(Operator):
303     '''Set a context value'''
304     bl_idname = "wm.context_set_value"
305     bl_label = "Context Set Value"
306     bl_options = {'UNDO', 'INTERNAL'}
307
308     data_path = rna_path_prop
309     value = StringProperty(
310             name="Value",
311             description="Assignment value (as a string)",
312             maxlen=1024,
313             )
314
315     def execute(self, context):
316         data_path = self.data_path
317         if context_path_validate(context, data_path) is Ellipsis:
318             return {'PASS_THROUGH'}
319         exec("context.%s = %s" % (data_path, self.value))
320         return operator_path_undo_return(context, data_path)
321
322
323 class WM_OT_context_toggle(Operator):
324     '''Toggle a context value'''
325     bl_idname = "wm.context_toggle"
326     bl_label = "Context Toggle"
327     bl_options = {'UNDO', 'INTERNAL'}
328
329     data_path = rna_path_prop
330
331     def execute(self, context):
332         data_path = self.data_path
333
334         if context_path_validate(context, data_path) is Ellipsis:
335             return {'PASS_THROUGH'}
336
337         exec("context.%s = not (context.%s)" % (data_path, data_path))
338
339         return operator_path_undo_return(context, data_path)
340
341
342 class WM_OT_context_toggle_enum(Operator):
343     '''Toggle a context value'''
344     bl_idname = "wm.context_toggle_enum"
345     bl_label = "Context Toggle Values"
346     bl_options = {'UNDO', 'INTERNAL'}
347
348     data_path = rna_path_prop
349     value_1 = StringProperty(
350             name="Value",
351             description="Toggle enum",
352             maxlen=1024,
353             )
354     value_2 = StringProperty(
355             name="Value",
356             description="Toggle enum",
357             maxlen=1024,
358             )
359
360     def execute(self, context):
361         data_path = self.data_path
362
363         if context_path_validate(context, data_path) is Ellipsis:
364             return {'PASS_THROUGH'}
365
366         exec("context.%s = ('%s', '%s')[context.%s != '%s']" %
367              (data_path, self.value_1,
368               self.value_2, data_path,
369               self.value_2,
370               ))
371
372         return operator_path_undo_return(context, data_path)
373
374
375 class WM_OT_context_cycle_int(Operator):
376     '''Set a context value. Useful for cycling active material, '''
377     '''vertex keys, groups' etc'''
378     bl_idname = "wm.context_cycle_int"
379     bl_label = "Context Int Cycle"
380     bl_options = {'UNDO', 'INTERNAL'}
381
382     data_path = rna_path_prop
383     reverse = rna_reverse_prop
384
385     def execute(self, context):
386         data_path = self.data_path
387         value = context_path_validate(context, data_path)
388         if value is Ellipsis:
389             return {'PASS_THROUGH'}
390
391         if self.reverse:
392             value -= 1
393         else:
394             value += 1
395
396         exec("context.%s = value" % data_path)
397
398         if value != eval("context.%s" % data_path):
399             # relies on rna clamping int's out of the range
400             if self.reverse:
401                 value = (1 << 31) - 1
402             else:
403                 value = -1 << 31
404
405             exec("context.%s = value" % data_path)
406
407         return operator_path_undo_return(context, data_path)
408
409
410 class WM_OT_context_cycle_enum(Operator):
411     '''Toggle a context value'''
412     bl_idname = "wm.context_cycle_enum"
413     bl_label = "Context Enum Cycle"
414     bl_options = {'UNDO', 'INTERNAL'}
415
416     data_path = rna_path_prop
417     reverse = rna_reverse_prop
418
419     def execute(self, context):
420         data_path = self.data_path
421         value = context_path_validate(context, data_path)
422         if value is Ellipsis:
423             return {'PASS_THROUGH'}
424
425         orig_value = value
426
427         # Have to get rna enum values
428         rna_struct_str, rna_prop_str = data_path.rsplit('.', 1)
429         i = rna_prop_str.find('[')
430
431         # just in case we get "context.foo.bar[0]"
432         if i != -1:
433             rna_prop_str = rna_prop_str[0:i]
434
435         rna_struct = eval("context.%s.rna_type" % rna_struct_str)
436
437         rna_prop = rna_struct.properties[rna_prop_str]
438
439         if type(rna_prop) != bpy.types.EnumProperty:
440             raise Exception("expected an enum property")
441
442         enums = rna_struct.properties[rna_prop_str].enum_items.keys()
443         orig_index = enums.index(orig_value)
444
445         # Have the info we need, advance to the next item
446         if self.reverse:
447             if orig_index == 0:
448                 advance_enum = enums[-1]
449             else:
450                 advance_enum = enums[orig_index - 1]
451         else:
452             if orig_index == len(enums) - 1:
453                 advance_enum = enums[0]
454             else:
455                 advance_enum = enums[orig_index + 1]
456
457         # set the new value
458         exec("context.%s = advance_enum" % data_path)
459         return operator_path_undo_return(context, data_path)
460
461
462 class WM_OT_context_cycle_array(Operator):
463     '''Set a context array value. '''
464     '''Useful for cycling the active mesh edit mode'''
465     bl_idname = "wm.context_cycle_array"
466     bl_label = "Context Array Cycle"
467     bl_options = {'UNDO', 'INTERNAL'}
468
469     data_path = rna_path_prop
470     reverse = rna_reverse_prop
471
472     def execute(self, context):
473         data_path = self.data_path
474         value = context_path_validate(context, data_path)
475         if value is Ellipsis:
476             return {'PASS_THROUGH'}
477
478         def cycle(array):
479             if self.reverse:
480                 array.insert(0, array.pop())
481             else:
482                 array.append(array.pop(0))
483             return array
484
485         exec("context.%s = cycle(context.%s[:])" % (data_path, data_path))
486
487         return operator_path_undo_return(context, data_path)
488
489
490 class WM_MT_context_menu_enum(Menu):
491     bl_label = ""
492     data_path = ""  # BAD DESIGN, set from operator below.
493
494     def draw(self, context):
495         data_path = self.data_path
496         value = context_path_validate(bpy.context, data_path)
497         if value is Ellipsis:
498             return {'PASS_THROUGH'}
499         base_path, prop_string = data_path.rsplit(".", 1)
500         value_base = context_path_validate(context, base_path)
501
502         values = [(i.name, i.identifier) for i in value_base.bl_rna.properties[prop_string].enum_items]
503
504         for name, identifier in values:
505             prop = self.layout.operator("wm.context_set_enum", text=name)
506             prop.data_path = data_path
507             prop.value = identifier
508
509
510 class WM_OT_context_menu_enum(Operator):
511     bl_idname = "wm.context_menu_enum"
512     bl_label = "Context Enum Menu"
513     bl_options = {'UNDO', 'INTERNAL'}
514     data_path = rna_path_prop
515
516     def execute(self, context):
517         data_path = self.data_path
518         WM_MT_context_menu_enum.data_path = data_path
519         bpy.ops.wm.call_menu(name="WM_MT_context_menu_enum")
520         return {'PASS_THROUGH'}
521
522
523 class WM_OT_context_set_id(Operator):
524     '''Toggle a context value'''
525     bl_idname = "wm.context_set_id"
526     bl_label = "Set Library ID"
527     bl_options = {'UNDO', 'INTERNAL'}
528
529     data_path = rna_path_prop
530     value = StringProperty(
531             name="Value",
532             description="Assign value",
533             maxlen=1024,
534             )
535
536     def execute(self, context):
537         value = self.value
538         data_path = self.data_path
539
540         # match the pointer type from the target property to bpy.data.*
541         # so we lookup the correct list.
542         data_path_base, data_path_prop = data_path.rsplit(".", 1)
543         data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
544         data_prop_rna_type = data_prop_rna.fixed_type
545
546         id_iter = None
547
548         for prop in bpy.data.rna_type.properties:
549             if prop.rna_type.identifier == "CollectionProperty":
550                 if prop.fixed_type == data_prop_rna_type:
551                     id_iter = prop.identifier
552                     break
553
554         if id_iter:
555             value_id = getattr(bpy.data, id_iter).get(value)
556             exec("context.%s = value_id" % data_path)
557
558         return operator_path_undo_return(context, data_path)
559
560
561 doc_id = StringProperty(
562         name="Doc ID",
563         maxlen=1024,
564         options={'HIDDEN'},
565         )
566
567 doc_new = StringProperty(
568         name="Edit Description",
569         maxlen=1024,
570         )
571
572 data_path_iter = StringProperty(
573         description="The data path relative to the context, must point to an iterable")
574
575 data_path_item = StringProperty(
576         description="The data path from each iterable to the value (int or float)")
577
578
579 class WM_OT_context_collection_boolean_set(Operator):
580     '''Set boolean values for a collection of items'''
581     bl_idname = "wm.context_collection_boolean_set"
582     bl_label = "Context Collection Boolean Set"
583     bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
584
585     data_path_iter = data_path_iter
586     data_path_item = data_path_item
587
588     type = EnumProperty(
589             name="Type",
590             items=(('TOGGLE', "Toggle", ""),
591                    ('ENABLE', "Enable", ""),
592                    ('DISABLE', "Disable", ""),
593                    ),
594             )
595
596     def execute(self, context):
597         data_path_iter = self.data_path_iter
598         data_path_item = self.data_path_item
599
600         items = list(getattr(context, data_path_iter))
601         items_ok = []
602         is_set = False
603         for item in items:
604             try:
605                 value_orig = eval("item." + data_path_item)
606             except:
607                 continue
608
609             if value_orig == True:
610                 is_set = True
611             elif value_orig == False:
612                 pass
613             else:
614                 self.report({'WARNING'}, "Non boolean value found: %s[ ].%s" %
615                             (data_path_iter, data_path_item))
616                 return {'CANCELLED'}
617
618             items_ok.append(item)
619
620         # avoid undo push when nothing to do
621         if not items_ok:
622             return {'CANCELLED'}
623
624         if self.type == 'ENABLE':
625             is_set = True
626         elif self.type == 'DISABLE':
627             is_set = False
628         else:
629             is_set = not is_set
630
631         exec_str = "item.%s = %s" % (data_path_item, is_set)
632         for item in items_ok:
633             exec(exec_str)
634
635         return operator_value_undo_return(item)
636
637
638 class WM_OT_context_modal_mouse(Operator):
639     '''Adjust arbitrary values with mouse input'''
640     bl_idname = "wm.context_modal_mouse"
641     bl_label = "Context Modal Mouse"
642     bl_options = {'GRAB_POINTER', 'BLOCKING', 'UNDO', 'INTERNAL'}
643
644     data_path_iter = data_path_iter
645     data_path_item = data_path_item
646
647     input_scale = FloatProperty(
648             description="Scale the mouse movement by this value before applying the delta",
649             default=0.01,
650             )
651     invert = BoolProperty(
652             description="Invert the mouse input",
653             default=False,
654             )
655     initial_x = IntProperty(options={'HIDDEN'})
656
657     def _values_store(self, context):
658         data_path_iter = self.data_path_iter
659         data_path_item = self.data_path_item
660
661         self._values = values = {}
662
663         for item in getattr(context, data_path_iter):
664             try:
665                 value_orig = eval("item." + data_path_item)
666             except:
667                 continue
668
669             # check this can be set, maybe this is library data.
670             try:
671                 exec("item.%s = %s" % (data_path_item, value_orig))
672             except:
673                 continue
674
675             values[item] = value_orig
676
677     def _values_delta(self, delta):
678         delta *= self.input_scale
679         if self.invert:
680             delta = - delta
681
682         data_path_item = self.data_path_item
683         for item, value_orig in self._values.items():
684             if type(value_orig) == int:
685                 exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
686             else:
687                 exec("item.%s = %f" % (data_path_item, value_orig + delta))
688
689     def _values_restore(self):
690         data_path_item = self.data_path_item
691         for item, value_orig in self._values.items():
692             exec("item.%s = %s" % (data_path_item, value_orig))
693
694         self._values.clear()
695
696     def _values_clear(self):
697         self._values.clear()
698
699     def modal(self, context, event):
700         event_type = event.type
701
702         if event_type == 'MOUSEMOVE':
703             delta = event.mouse_x - self.initial_x
704             self._values_delta(delta)
705
706         elif 'LEFTMOUSE' == event_type:
707             item = next(iter(self._values.keys()))
708             self._values_clear()
709             return operator_value_undo_return(item)
710
711         elif event_type in {'RIGHTMOUSE', 'ESC'}:
712             self._values_restore()
713             return {'CANCELLED'}
714
715         return {'RUNNING_MODAL'}
716
717     def invoke(self, context, event):
718         self._values_store(context)
719
720         if not self._values:
721             self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
722                     (self.data_path_iter, self.data_path_item))
723
724             return {'CANCELLED'}
725         else:
726             self.initial_x = event.mouse_x
727
728             context.window_manager.modal_handler_add(self)
729             return {'RUNNING_MODAL'}
730
731
732 class WM_OT_url_open(Operator):
733     "Open a website in the Webbrowser"
734     bl_idname = "wm.url_open"
735     bl_label = ""
736
737     url = StringProperty(
738             name="URL",
739             description="URL to open",
740             )
741
742     def execute(self, context):
743         import webbrowser
744         webbrowser.open(self.url)
745         return {'FINISHED'}
746
747
748 class WM_OT_path_open(Operator):
749     "Open a path in a file browser"
750     bl_idname = "wm.path_open"
751     bl_label = ""
752
753     filepath = StringProperty(
754             subtype='FILE_PATH',
755             )
756
757     def execute(self, context):
758         import sys
759         import os
760         import subprocess
761
762         filepath = bpy.path.abspath(self.filepath)
763         filepath = os.path.normpath(filepath)
764
765         if not os.path.exists(filepath):
766             self.report({'ERROR'}, "File '%s' not found" % filepath)
767             return {'CANCELLED'}
768
769         if sys.platform[:3] == "win":
770             subprocess.Popen(["start", filepath], shell=True)
771         elif sys.platform == "darwin":
772             subprocess.Popen(["open", filepath])
773         else:
774             try:
775                 subprocess.Popen(["xdg-open", filepath])
776             except OSError:
777                 # xdg-open *should* be supported by recent Gnome, KDE, Xfce
778                 pass
779
780         return {'FINISHED'}
781
782
783 class WM_OT_doc_view(Operator):
784     '''Load online reference docs'''
785     bl_idname = "wm.doc_view"
786     bl_label = "View Documentation"
787
788     doc_id = doc_id
789     if bpy.app.version_cycle == "release":
790         _prefix = ("http://www.blender.org/documentation/blender_python_api_%s%s_release" %
791                    ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char))
792     else:
793         _prefix = ("http://www.blender.org/documentation/blender_python_api_%s" %
794                    "_".join(str(v) for v in bpy.app.version))
795
796     def _nested_class_string(self, class_string):
797         ls = []
798         class_obj = getattr(bpy.types, class_string, None).bl_rna
799         while class_obj:
800             ls.insert(0, class_obj)
801             class_obj = class_obj.nested
802         return '.'.join(class_obj.identifier for class_obj in ls)
803
804     def execute(self, context):
805         id_split = self.doc_id.split('.')
806         if len(id_split) == 1:  # rna, class
807             url = '%s/bpy.types.%s.html' % (self._prefix, id_split[0])
808         elif len(id_split) == 2:  # rna, class.prop
809             class_name, class_prop = id_split
810
811             if hasattr(bpy.types, class_name.upper() + '_OT_' + class_prop):
812                 url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
813                        (self._prefix, class_name, class_name, class_prop))
814             else:
815
816                 # detect if this is a inherited member and use that name instead
817                 rna_parent = getattr(bpy.types, class_name).bl_rna
818                 rna_prop = rna_parent.properties[class_prop]
819                 rna_parent = rna_parent.base
820                 while rna_parent and rna_prop == rna_parent.properties.get(class_prop):
821                     class_name = rna_parent.identifier
822                     rna_parent = rna_parent.base
823
824                 #~ class_name_full = self._nested_class_string(class_name)
825                 url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" %
826                        (self._prefix, class_name, class_name, class_prop))
827
828         else:
829             return {'PASS_THROUGH'}
830
831         import webbrowser
832         webbrowser.open(url)
833
834         return {'FINISHED'}
835
836
837 class WM_OT_doc_edit(Operator):
838     '''Load online reference docs'''
839     bl_idname = "wm.doc_edit"
840     bl_label = "Edit Documentation"
841
842     doc_id = doc_id
843     doc_new = doc_new
844
845     _url = "http://www.mindrones.com/blender/svn/xmlrpc.php"
846
847     def _send_xmlrpc(self, data_dict):
848         print("sending data:", data_dict)
849
850         import xmlrpc.client
851         user = "blenderuser"
852         pwd = "blender>user"
853
854         docblog = xmlrpc.client.ServerProxy(self._url)
855         docblog.metaWeblog.newPost(1, user, pwd, data_dict, 1)
856
857     def execute(self, context):
858
859         doc_id = self.doc_id
860         doc_new = self.doc_new
861
862         class_name, class_prop = doc_id.split('.')
863
864         if not doc_new:
865             self.report({'ERROR'}, "No input given for '%s'" % doc_id)
866             return {'CANCELLED'}
867
868         # check if this is an operator
869         op_name = class_name.upper() + '_OT_' + class_prop
870         op_class = getattr(bpy.types, op_name, None)
871
872         # Upload this to the web server
873         upload = {}
874
875         if op_class:
876             rna = op_class.bl_rna
877             doc_orig = rna.description
878             if doc_orig == doc_new:
879                 return {'RUNNING_MODAL'}
880
881             print("op - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
882             upload["title"] = 'OPERATOR %s:%s' % (doc_id, doc_orig)
883         else:
884             rna = getattr(bpy.types, class_name).bl_rna
885             doc_orig = rna.properties[class_prop].description
886             if doc_orig == doc_new:
887                 return {'RUNNING_MODAL'}
888
889             print("rna - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
890             upload["title"] = 'RNA %s:%s' % (doc_id, doc_orig)
891
892         upload["description"] = doc_new
893
894         self._send_xmlrpc(upload)
895
896         return {'FINISHED'}
897
898     def draw(self, context):
899         layout = self.layout
900         layout.label(text="Descriptor ID: '%s'" % self.doc_id)
901         layout.prop(self, "doc_new", text="")
902
903     def invoke(self, context, event):
904         wm = context.window_manager
905         return wm.invoke_props_dialog(self, width=600)
906
907
908 rna_path = StringProperty(
909         name="Property Edit",
910         description="Property data_path edit",
911         maxlen=1024,
912         options={'HIDDEN'},
913         )
914
915 rna_value = StringProperty(
916         name="Property Value",
917         description="Property value edit",
918         maxlen=1024,
919         )
920
921 rna_property = StringProperty(
922         name="Property Name",
923         description="Property name edit",
924         maxlen=1024,
925         )
926
927 rna_min = FloatProperty(
928         name="Min",
929         default=0.0,
930         precision=3,
931         )
932
933 rna_max = FloatProperty(
934         name="Max",
935         default=1.0,
936         precision=3,
937         )
938
939
940 class WM_OT_properties_edit(Operator):
941     '''Internal use (edit a property data_path)'''
942     bl_idname = "wm.properties_edit"
943     bl_label = "Edit Property"
944     bl_options = {'REGISTER'}  # only because invoke_props_popup requires.
945
946     data_path = rna_path
947     property = rna_property
948     value = rna_value
949     min = rna_min
950     max = rna_max
951     description = StringProperty(
952             name="Tip",
953             )
954
955     def execute(self, context):
956         data_path = self.data_path
957         value = self.value
958         prop = self.property
959
960         prop_old = getattr(self, "_last_prop", [None])[0]
961
962         if prop_old is None:
963             self.report({'ERROR'}, "Direct execution not supported")
964             return {'CANCELLED'}
965
966         try:
967             value_eval = eval(value)
968         except:
969             value_eval = value
970
971         # First remove
972         item = eval("context.%s" % data_path)
973
974         rna_idprop_ui_prop_clear(item, prop_old)
975         exec_str = "del item['%s']" % prop_old
976         # print(exec_str)
977         exec(exec_str)
978
979         # Reassign
980         exec_str = "item['%s'] = %s" % (prop, repr(value_eval))
981         # print(exec_str)
982         exec(exec_str)
983         self._last_prop[:] = [prop]
984
985         prop_type = type(item[prop])
986
987         prop_ui = rna_idprop_ui_prop_get(item, prop)
988
989         if prop_type in {float, int}:
990             prop_ui["soft_min"] = prop_ui["min"] = prop_type(self.min)
991             prop_ui["soft_max"] = prop_ui["max"] = prop_type(self.max)
992
993         prop_ui['description'] = self.description
994
995         # otherwise existing buttons which reference freed
996         # memory may crash blender [#26510]
997         # context.area.tag_redraw()
998         for win in context.window_manager.windows:
999             for area in win.screen.areas:
1000                 area.tag_redraw()
1001
1002         return {'FINISHED'}
1003
1004     def invoke(self, context, event):
1005         data_path = self.data_path
1006
1007         if not data_path:
1008             self.report({'ERROR'}, "Data path not set")
1009             return {'CANCELLED'}
1010
1011         self._last_prop = [self.property]
1012
1013         item = eval("context.%s" % data_path)
1014
1015         # setup defaults
1016         prop_ui = rna_idprop_ui_prop_get(item, self.property, False)  # don't create
1017         if prop_ui:
1018             self.min = prop_ui.get("min", -1000000000)
1019             self.max = prop_ui.get("max", 1000000000)
1020             self.description = prop_ui.get("description", "")
1021
1022         wm = context.window_manager
1023         return wm.invoke_props_dialog(self)
1024
1025
1026 class WM_OT_properties_add(Operator):
1027     '''Internal use (edit a property data_path)'''
1028     bl_idname = "wm.properties_add"
1029     bl_label = "Add Property"
1030
1031     data_path = rna_path
1032
1033     def execute(self, context):
1034         data_path = self.data_path
1035         item = eval("context.%s" % data_path)
1036
1037         def unique_name(names):
1038             prop = "prop"
1039             prop_new = prop
1040             i = 1
1041             while prop_new in names:
1042                 prop_new = prop + str(i)
1043                 i += 1
1044
1045             return prop_new
1046
1047         property = unique_name(item.keys())
1048
1049         item[property] = 1.0
1050         return {'FINISHED'}
1051
1052
1053 class WM_OT_properties_context_change(Operator):
1054     "Change the context tab in a Properties Window"
1055     bl_idname = "wm.properties_context_change"
1056     bl_label = ""
1057
1058     context = StringProperty(
1059             name="Context",
1060             maxlen=32,
1061             )
1062
1063     def execute(self, context):
1064         context.space_data.context = self.context
1065         return {'FINISHED'}
1066
1067
1068 class WM_OT_properties_remove(Operator):
1069     '''Internal use (edit a property data_path)'''
1070     bl_idname = "wm.properties_remove"
1071     bl_label = "Remove Property"
1072
1073     data_path = rna_path
1074     property = rna_property
1075
1076     def execute(self, context):
1077         data_path = self.data_path
1078         item = eval("context.%s" % data_path)
1079         del item[self.property]
1080         return {'FINISHED'}
1081
1082
1083 class WM_OT_keyconfig_activate(Operator):
1084     bl_idname = "wm.keyconfig_activate"
1085     bl_label = "Activate Keyconfig"
1086
1087     filepath = StringProperty(
1088             subtype='FILE_PATH',
1089             )
1090
1091     def execute(self, context):
1092         bpy.utils.keyconfig_set(self.filepath)
1093         return {'FINISHED'}
1094
1095
1096 class WM_OT_appconfig_default(Operator):
1097     bl_idname = "wm.appconfig_default"
1098     bl_label = "Default Application Configuration"
1099
1100     def execute(self, context):
1101         import os
1102
1103         context.window_manager.keyconfigs.active = context.window_manager.keyconfigs.default
1104
1105         filepath = os.path.join(bpy.utils.preset_paths("interaction")[0], "blender.py")
1106
1107         if os.path.exists(filepath):
1108             bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
1109
1110         return {'FINISHED'}
1111
1112
1113 class WM_OT_appconfig_activate(Operator):
1114     bl_idname = "wm.appconfig_activate"
1115     bl_label = "Activate Application Configuration"
1116
1117     filepath = StringProperty(
1118             subtype='FILE_PATH',
1119             )
1120
1121     def execute(self, context):
1122         import os
1123         bpy.utils.keyconfig_set(self.filepath)
1124
1125         filepath = self.filepath.replace("keyconfig", "interaction")
1126
1127         if os.path.exists(filepath):
1128             bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
1129
1130         return {'FINISHED'}
1131
1132
1133 class WM_OT_sysinfo(Operator):
1134     '''Generate System Info'''
1135     bl_idname = "wm.sysinfo"
1136     bl_label = "System Info"
1137
1138     def execute(self, context):
1139         import sys_info
1140         sys_info.write_sysinfo(self)
1141         return {'FINISHED'}
1142
1143
1144 class WM_OT_copy_prev_settings(Operator):
1145     '''Copy settings from previous version'''
1146     bl_idname = "wm.copy_prev_settings"
1147     bl_label = "Copy Previous Settings"
1148
1149     def execute(self, context):
1150         import os
1151         import shutil
1152         ver = bpy.app.version
1153         ver_old = ((ver[0] * 100) + ver[1]) - 1
1154         path_src = bpy.utils.resource_path('USER', ver_old // 100, ver_old % 100)
1155         path_dst = bpy.utils.resource_path('USER')
1156
1157         if os.path.isdir(path_dst):
1158             self.report({'ERROR'}, "Target path %r exists" % path_dst)
1159         elif not os.path.isdir(path_src):
1160             self.report({'ERROR'}, "Source path %r exists" % path_src)
1161         else:
1162             shutil.copytree(path_src, path_dst, symlinks=True)
1163
1164             # in 2.57 and earlier windows installers, system scripts were copied
1165             # into the configuration directory, don't want to copy those
1166             system_script = os.path.join(path_dst, "scripts/modules/bpy_types.py")
1167             if os.path.isfile(system_script):
1168                 shutil.rmtree(os.path.join(path_dst, "scripts"))
1169                 shutil.rmtree(os.path.join(path_dst, "plugins"))
1170
1171             # don't loose users work if they open the splash later.
1172             if bpy.data.is_saved is bpy.data.is_dirty is False:
1173                 bpy.ops.wm.read_homefile()
1174             else:
1175                 self.report({'INFO'}, "Reload Start-Up file to restore settings")
1176             return {'FINISHED'}
1177
1178         return {'CANCELLED'}
1179
1180
1181 class WM_OT_blenderplayer_start(bpy.types.Operator):
1182     '''Launch the Blenderplayer with the current blendfile'''
1183     bl_idname = "wm.blenderplayer_start"
1184     bl_label = "Start"
1185
1186     blender_bin_path = bpy.app.binary_path
1187     blender_bin_dir = os.path.dirname(blender_bin_path)
1188     ext = os.path.splitext(blender_bin_path)[-1]
1189     player_path = os.path.join(blender_bin_dir, "blenderplayer" + ext)
1190
1191     def execute(self, context):
1192         import sys
1193
1194         if sys.platform == "darwin":
1195             self.player_path = os.path.join(self.blender_bin_dir, "../../../blenderplayer.app/Contents/MacOS/blenderplayer")
1196
1197         filepath = bpy.app.tempdir + "game.blend"
1198         bpy.ops.wm.save_as_mainfile(filepath=filepath, check_existing=False, copy=True)
1199         subprocess.call([self.player_path, filepath])
1200         return {'FINISHED'}
1201
1202
1203 class WM_OT_keyconfig_test(Operator):
1204     "Test keyconfig for conflicts"
1205     bl_idname = "wm.keyconfig_test"
1206     bl_label = "Test Key Configuration for Conflicts"
1207
1208     def execute(self, context):
1209         from bpy_extras import keyconfig_utils
1210
1211         wm = context.window_manager
1212         kc = wm.keyconfigs.default
1213
1214         if keyconfig_utils.keyconfig_test(kc):
1215             print("CONFLICT")
1216
1217         return {'FINISHED'}
1218
1219
1220 class WM_OT_keyconfig_import(Operator):
1221     "Import key configuration from a python script"
1222     bl_idname = "wm.keyconfig_import"
1223     bl_label = "Import Key Configuration..."
1224
1225     filepath = StringProperty(
1226             subtype='FILE_PATH',
1227             default="keymap.py",
1228             )
1229     filter_folder = BoolProperty(
1230             name="Filter folders",
1231             default=True,
1232             options={'HIDDEN'},
1233             )
1234     filter_text = BoolProperty(
1235             name="Filter text",
1236             default=True,
1237             options={'HIDDEN'},
1238             )
1239     filter_python = BoolProperty(
1240             name="Filter python",
1241             default=True,
1242             options={'HIDDEN'},
1243             )
1244     keep_original = BoolProperty(
1245             name="Keep original",
1246             description="Keep original file after copying to configuration folder",
1247             default=True,
1248             )
1249
1250     def execute(self, context):
1251         import os
1252         from os.path import basename
1253         import shutil
1254
1255         if not self.filepath:
1256             self.report({'ERROR'}, "Filepath not set")
1257             return {'CANCELLED'}
1258
1259         config_name = basename(self.filepath)
1260
1261         path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True)
1262         path = os.path.join(path, config_name)
1263
1264         try:
1265             if self.keep_original:
1266                 shutil.copy(self.filepath, path)
1267             else:
1268                 shutil.move(self.filepath, path)
1269         except Exception as e:
1270             self.report({'ERROR'}, "Installing keymap failed: %s" % e)
1271             return {'CANCELLED'}
1272
1273         # sneaky way to check we're actually running the code.
1274         bpy.utils.keyconfig_set(path)
1275
1276         return {'FINISHED'}
1277
1278     def invoke(self, context, event):
1279         wm = context.window_manager
1280         wm.fileselect_add(self)
1281         return {'RUNNING_MODAL'}
1282
1283 # This operator is also used by interaction presets saving - AddPresetBase
1284
1285
1286 class WM_OT_keyconfig_export(Operator):
1287     "Export key configuration to a python script"
1288     bl_idname = "wm.keyconfig_export"
1289     bl_label = "Export Key Configuration..."
1290
1291     filepath = StringProperty(
1292             subtype='FILE_PATH',
1293             default="keymap.py",
1294             )
1295     filter_folder = BoolProperty(
1296             name="Filter folders",
1297             default=True,
1298             options={'HIDDEN'},
1299             )
1300     filter_text = BoolProperty(
1301             name="Filter text",
1302             default=True,
1303             options={'HIDDEN'},
1304             )
1305     filter_python = BoolProperty(
1306             name="Filter python",
1307             default=True,
1308             options={'HIDDEN'},
1309             )
1310
1311     def execute(self, context):
1312         from bpy_extras import keyconfig_utils
1313
1314         if not self.filepath:
1315             raise Exception("Filepath not set")
1316
1317         if not self.filepath.endswith('.py'):
1318             self.filepath += '.py'
1319
1320         wm = context.window_manager
1321
1322         keyconfig_utils.keyconfig_export(wm,
1323                                          wm.keyconfigs.active,
1324                                          self.filepath,
1325                                          )
1326
1327         return {'FINISHED'}
1328
1329     def invoke(self, context, event):
1330         wm = context.window_manager
1331         wm.fileselect_add(self)
1332         return {'RUNNING_MODAL'}
1333
1334
1335 class WM_OT_keymap_restore(Operator):
1336     "Restore key map(s)"
1337     bl_idname = "wm.keymap_restore"
1338     bl_label = "Restore Key Map(s)"
1339
1340     all = BoolProperty(
1341             name="All Keymaps",
1342             description="Restore all keymaps to default",
1343             )
1344
1345     def execute(self, context):
1346         wm = context.window_manager
1347
1348         if self.all:
1349             for km in wm.keyconfigs.user.keymaps:
1350                 km.restore_to_default()
1351         else:
1352             km = context.keymap
1353             km.restore_to_default()
1354
1355         return {'FINISHED'}
1356
1357
1358 class WM_OT_keyitem_restore(Operator):
1359     "Restore key map item"
1360     bl_idname = "wm.keyitem_restore"
1361     bl_label = "Restore Key Map Item"
1362
1363     item_id = IntProperty(
1364             name="Item Identifier",
1365             description="Identifier of the item to remove",
1366             )
1367
1368     @classmethod
1369     def poll(cls, context):
1370         keymap = getattr(context, "keymap", None)
1371         return keymap
1372
1373     def execute(self, context):
1374         km = context.keymap
1375         kmi = km.keymap_items.from_id(self.item_id)
1376
1377         if (not kmi.is_user_defined) and kmi.is_user_modified:
1378             km.restore_item_to_default(kmi)
1379
1380         return {'FINISHED'}
1381
1382
1383 class WM_OT_keyitem_add(Operator):
1384     "Add key map item"
1385     bl_idname = "wm.keyitem_add"
1386     bl_label = "Add Key Map Item"
1387
1388     def execute(self, context):
1389         km = context.keymap
1390
1391         if km.is_modal:
1392             km.keymap_items.new_modal("", 'A', 'PRESS')
1393         else:
1394             km.keymap_items.new("none", 'A', 'PRESS')
1395
1396         # clear filter and expand keymap so we can see the newly added item
1397         if context.space_data.filter_text != "":
1398             context.space_data.filter_text = ""
1399             km.show_expanded_items = True
1400             km.show_expanded_children = True
1401
1402         return {'FINISHED'}
1403
1404
1405 class WM_OT_keyitem_remove(Operator):
1406     "Remove key map item"
1407     bl_idname = "wm.keyitem_remove"
1408     bl_label = "Remove Key Map Item"
1409
1410     item_id = IntProperty(
1411             name="Item Identifier",
1412             description="Identifier of the item to remove",
1413             )
1414
1415     @classmethod
1416     def poll(cls, context):
1417         return hasattr(context, "keymap")
1418
1419     def execute(self, context):
1420         km = context.keymap
1421         kmi = km.keymap_items.from_id(self.item_id)
1422         km.keymap_items.remove(kmi)
1423         return {'FINISHED'}
1424
1425
1426 class WM_OT_keyconfig_remove(Operator):
1427     "Remove key config"
1428     bl_idname = "wm.keyconfig_remove"
1429     bl_label = "Remove Key Config"
1430
1431     @classmethod
1432     def poll(cls, context):
1433         wm = context.window_manager
1434         keyconf = wm.keyconfigs.active
1435         return keyconf and keyconf.is_user_defined
1436
1437     def execute(self, context):
1438         wm = context.window_manager
1439         keyconfig = wm.keyconfigs.active
1440         wm.keyconfigs.remove(keyconfig)
1441         return {'FINISHED'}
1442
1443
1444 class WM_OT_operator_cheat_sheet(Operator):
1445     bl_idname = "wm.operator_cheat_sheet"
1446     bl_label = "Operator Cheat Sheet"
1447
1448     def execute(self, context):
1449         op_strings = []
1450         tot = 0
1451         for op_module_name in dir(bpy.ops):
1452             op_module = getattr(bpy.ops, op_module_name)
1453             for op_submodule_name in dir(op_module):
1454                 op = getattr(op_module, op_submodule_name)
1455                 text = repr(op)
1456                 if text.split("\n")[-1].startswith("bpy.ops."):
1457                     op_strings.append(text)
1458                     tot += 1
1459
1460             op_strings.append('')
1461
1462         textblock = bpy.data.texts.new("OperatorList.txt")
1463         textblock.write('# %d Operators\n\n' % tot)
1464         textblock.write('\n'.join(op_strings))
1465         self.report({'INFO'}, "See OperatorList.txt textblock")
1466         return {'FINISHED'}
1467
1468
1469 # -----------------------------------------------------------------------------
1470 # Addon Operators
1471
1472 class WM_OT_addon_enable(Operator):
1473     "Enable an addon"
1474     bl_idname = "wm.addon_enable"
1475     bl_label = "Enable Addon"
1476
1477     module = StringProperty(
1478             name="Module",
1479             description="Module name of the addon to enable",
1480             )
1481
1482     def execute(self, context):
1483         import addon_utils
1484
1485         mod = addon_utils.enable(self.module)
1486
1487         if mod:
1488             info = addon_utils.module_bl_info(mod)
1489
1490             info_ver = info.get("blender", (0, 0, 0))
1491
1492             if info_ver > bpy.app.version:
1493                 self.report({'WARNING'}, ("This script was written Blender "
1494                                           "version %d.%d.%d and might not "
1495                                           "function (correctly), "
1496                                           "though it is enabled") %
1497                                          info_ver)
1498             return {'FINISHED'}
1499         else:
1500             return {'CANCELLED'}
1501
1502
1503 class WM_OT_addon_disable(Operator):
1504     "Disable an addon"
1505     bl_idname = "wm.addon_disable"
1506     bl_label = "Disable Addon"
1507
1508     module = StringProperty(
1509             name="Module",
1510             description="Module name of the addon to disable",
1511             )
1512
1513     def execute(self, context):
1514         import addon_utils
1515
1516         addon_utils.disable(self.module)
1517         return {'FINISHED'}
1518
1519
1520 class WM_OT_addon_install(Operator):
1521     "Install an addon"
1522     bl_idname = "wm.addon_install"
1523     bl_label = "Install Addon..."
1524
1525     overwrite = BoolProperty(
1526             name="Overwrite",
1527             description="Remove existing addons with the same ID",
1528             default=True,
1529             )
1530     target = EnumProperty(
1531             name="Target Path",
1532             items=(('DEFAULT', "Default", ""),
1533                    ('PREFS', "User Prefs", "")),
1534             )
1535
1536     filepath = StringProperty(
1537             subtype='FILE_PATH',
1538             )
1539     filter_folder = BoolProperty(
1540             name="Filter folders",
1541             default=True,
1542             options={'HIDDEN'},
1543             )
1544     filter_python = BoolProperty(
1545             name="Filter python",
1546             default=True,
1547             options={'HIDDEN'},
1548             )
1549     filter_glob = StringProperty(
1550             default="*.py;*.zip",
1551             options={'HIDDEN'},
1552             )
1553
1554     @staticmethod
1555     def _module_remove(path_addons, module):
1556         import os
1557         module = os.path.splitext(module)[0]
1558         for f in os.listdir(path_addons):
1559             f_base = os.path.splitext(f)[0]
1560             if f_base == module:
1561                 f_full = os.path.join(path_addons, f)
1562
1563                 if os.path.isdir(f_full):
1564                     os.rmdir(f_full)
1565                 else:
1566                     os.remove(f_full)
1567
1568     def execute(self, context):
1569         import addon_utils
1570         import traceback
1571         import zipfile
1572         import shutil
1573         import os
1574
1575         pyfile = self.filepath
1576
1577         if self.target == 'DEFAULT':
1578             # don't use bpy.utils.script_paths("addons") because we may not be able to write to it.
1579             path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True)
1580         else:
1581             path_addons = bpy.context.user_preferences.filepaths.script_directory
1582             if path_addons:
1583                 path_addons = os.path.join(path_addons, "addons")
1584
1585         if not path_addons:
1586             self.report({'ERROR'}, "Failed to get addons path")
1587             return {'CANCELLED'}
1588
1589         # create dir is if missing.
1590         if not os.path.exists(path_addons):
1591             os.makedirs(path_addons)
1592
1593         # Check if we are installing from a target path,
1594         # doing so causes 2+ addons of same name or when the same from/to
1595         # location is used, removal of the file!
1596         addon_path = ""
1597         pyfile_dir = os.path.dirname(pyfile)
1598         for addon_path in addon_utils.paths():
1599             if os.path.samefile(pyfile_dir, addon_path):
1600                 self.report({'ERROR'}, "Source file is in the addon search path: %r" % addon_path)
1601                 return {'CANCELLED'}
1602         del addon_path
1603         del pyfile_dir
1604         # done checking for exceptional case
1605
1606         addons_old = {mod.__name__ for mod in addon_utils.modules(addon_utils.addons_fake_modules)}
1607
1608         #check to see if the file is in compressed format (.zip)
1609         if zipfile.is_zipfile(pyfile):
1610             try:
1611                 file_to_extract = zipfile.ZipFile(pyfile, 'r')
1612             except:
1613                 traceback.print_exc()
1614                 return {'CANCELLED'}
1615
1616             if self.overwrite:
1617                 for f in file_to_extract.namelist():
1618                     WM_OT_addon_install._module_remove(path_addons, f)
1619             else:
1620                 for f in file_to_extract.namelist():
1621                     path_dest = os.path.join(path_addons, os.path.basename(f))
1622                     if os.path.exists(path_dest):
1623                         self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
1624                         return {'CANCELLED'}
1625
1626             try:  # extract the file to "addons"
1627                 file_to_extract.extractall(path_addons)
1628
1629                 # zip files can create this dir with metadata, don't need it
1630                 macosx_dir = os.path.join(path_addons, '__MACOSX')
1631                 if os.path.isdir(macosx_dir):
1632                     shutil.rmtree(macosx_dir)
1633
1634             except:
1635                 traceback.print_exc()
1636                 return {'CANCELLED'}
1637
1638         else:
1639             path_dest = os.path.join(path_addons, os.path.basename(pyfile))
1640
1641             if self.overwrite:
1642                 WM_OT_addon_install._module_remove(path_addons, os.path.basename(pyfile))
1643             elif os.path.exists(path_dest):
1644                 self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
1645                 return {'CANCELLED'}
1646
1647             #if not compressed file just copy into the addon path
1648             try:
1649                 shutil.copyfile(pyfile, path_dest)
1650
1651             except:
1652                 traceback.print_exc()
1653                 return {'CANCELLED'}
1654
1655         addons_new = {mod.__name__ for mod in addon_utils.modules(addon_utils.addons_fake_modules)} - addons_old
1656         addons_new.discard("modules")
1657
1658         # disable any addons we may have enabled previously and removed.
1659         # this is unlikely but do just in case. bug [#23978]
1660         for new_addon in addons_new:
1661             addon_utils.disable(new_addon)
1662
1663         # possible the zip contains multiple addons, we could disallow this
1664         # but for now just use the first
1665         for mod in addon_utils.modules(addon_utils.addons_fake_modules):
1666             if mod.__name__ in addons_new:
1667                 info = addon_utils.module_bl_info(mod)
1668
1669                 # show the newly installed addon.
1670                 context.window_manager.addon_filter = 'All'
1671                 context.window_manager.addon_search = info["name"]
1672                 break
1673
1674         # in case a new module path was created to install this addon.
1675         bpy.utils.refresh_script_paths()
1676
1677         # TODO, should not be a warning.
1678         #~ self.report({'WARNING'}, "File installed to '%s'\n" % path_dest)
1679         return {'FINISHED'}
1680
1681     def invoke(self, context, event):
1682         wm = context.window_manager
1683         wm.fileselect_add(self)
1684         return {'RUNNING_MODAL'}
1685
1686
1687 class WM_OT_addon_remove(Operator):
1688     "Disable an addon"
1689     bl_idname = "wm.addon_remove"
1690     bl_label = "Remove Addon"
1691
1692     module = StringProperty(
1693             name="Module",
1694             description="Module name of the addon to remove",
1695             )
1696
1697     @staticmethod
1698     def path_from_addon(module):
1699         import os
1700         import addon_utils
1701
1702         for mod in addon_utils.modules(addon_utils.addons_fake_modules):
1703             if mod.__name__ == module:
1704                 filepath = mod.__file__
1705                 if os.path.exists(filepath):
1706                     if os.path.splitext(os.path.basename(filepath))[0] == "__init__":
1707                         return os.path.dirname(filepath), True
1708                     else:
1709                         return filepath, False
1710         return None, False
1711
1712     def execute(self, context):
1713         import addon_utils
1714         import os
1715
1716         path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
1717         if path is None:
1718             self.report('WARNING', "Addon path %r could not be found" % path)
1719             return {'CANCELLED'}
1720
1721         # in case its enabled
1722         addon_utils.disable(self.module)
1723
1724         import shutil
1725         if isdir:
1726             shutil.rmtree(path)
1727         else:
1728             os.remove(path)
1729
1730         context.area.tag_redraw()
1731         return {'FINISHED'}
1732
1733     # lame confirmation check
1734     def draw(self, context):
1735         self.layout.label(text="Remove Addon: %r?" % self.module)
1736         path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
1737         self.layout.label(text="Path: %r" % path)
1738
1739     def invoke(self, context, event):
1740         wm = context.window_manager
1741         return wm.invoke_props_dialog(self, width=600)
1742
1743
1744 class WM_OT_addon_expand(Operator):
1745     "Display more information on this addon"
1746     bl_idname = "wm.addon_expand"
1747     bl_label = ""
1748
1749     module = StringProperty(
1750             name="Module",
1751             description="Module name of the addon to expand",
1752             )
1753
1754     def execute(self, context):
1755         import addon_utils
1756
1757         module_name = self.module
1758
1759         # unlikely to fail, module should have already been imported
1760         try:
1761             # mod = __import__(module_name)
1762             mod = addon_utils.addons_fake_modules.get(module_name)
1763         except:
1764             import traceback
1765             traceback.print_exc()
1766             return {'CANCELLED'}
1767
1768         info = addon_utils.module_bl_info(mod)
1769         info["show_expanded"] = not info["show_expanded"]
1770         return {'FINISHED'}
1771
1772
1773 # -----------------------------------------------------------------------------
1774 # Theme IO
1775 from bpy_extras.io_utils import (ImportHelper,
1776                                  ExportHelper,
1777                                  )
1778
1779
1780 class WM_OT_theme_import(Operator, ImportHelper):
1781     bl_idname = "wm.theme_import"
1782     bl_label = "Import Theme"
1783     bl_options = {'REGISTER', 'UNDO'}
1784
1785     filename_ext = ".xml"
1786     filter_glob = StringProperty(default="*.xml", options={'HIDDEN'})
1787
1788     def execute(self, context):
1789         import rna_xml
1790         import xml.dom.minidom
1791
1792         filepath = self.filepath
1793
1794         xml_nodes = xml.dom.minidom.parse(filepath)
1795         theme_xml = xml_nodes.getElementsByTagName("Theme")[0]
1796
1797         # XXX, why always 0?, allow many?
1798         theme = context.user_preferences.themes[0]
1799
1800         rna_xml.xml2rna(theme_xml,
1801                         root_rna=theme,
1802                         )
1803
1804         return {'FINISHED'}
1805
1806
1807 class WM_OT_theme_export(Operator, ExportHelper):
1808     bl_idname = "wm.theme_export"
1809     bl_label = "Export Theme"
1810
1811     filename_ext = ".xml"
1812     filter_glob = StringProperty(default="*.xml", options={'HIDDEN'})
1813
1814     def execute(self, context):
1815         import rna_xml
1816
1817         filepath = self.filepath
1818         file = open(filepath, 'w', encoding='utf-8')
1819
1820         # XXX, why always 0?, allow many?
1821         theme = context.user_preferences.themes[0]
1822
1823         rna_xml.rna2xml(file.write,
1824                         root_rna=theme,
1825                         method='ATTR',
1826                         )
1827
1828         return {'FINISHED'}