Merging trunk up to r38932.
[blender.git] / release / scripts / startup / bl_ui / space_userpref.py
index 13ff09d..411060a 100644 (file)
@@ -94,7 +94,7 @@ class USERPREF_HT_header(bpy.types.Header):
             layout.operator("wm.keyconfig_import")
         elif userpref.active_section == 'ADDONS':
             layout.operator("wm.addon_install")
-            layout.menu("USERPREF_MT_addons_dev_guides", text="  Addons Developer Guides", icon='INFO')
+            layout.menu("USERPREF_MT_addons_dev_guides")
         elif userpref.active_section == 'THEMES':
             layout.operator("ui.reset_default_theme")
 
@@ -126,7 +126,7 @@ class USERPREF_MT_appconfigs(bpy.types.Menu):
     preset_operator = "wm.appconfig_activate"
 
     def draw(self, context):
-        props = self.layout.operator("wm.appconfig_default", text="Blender (default)")
+        self.layout.operator("wm.appconfig_default", text="Blender (default)")
 
         # now draw the presets
         bpy.types.Menu.draw_preset(self, context)
@@ -199,6 +199,7 @@ class USERPREF_PT_interface(bpy.types.Panel):
         col.prop(view, "use_zoom_to_mouse")
         col.prop(view, "use_rotate_around_active")
         col.prop(view, "use_global_pivot")
+        col.prop(view, "use_camera_lock_parent")
 
         col.separator()
 
@@ -437,6 +438,8 @@ class USERPREF_PT_system(bpy.types.Panel):
         col.label(text="OpenGL:")
         col.prop(system, "gl_clip_alpha", slider=True)
         col.prop(system, "use_mipmaps")
+        col.label(text="Anisotropic Filtering")
+        col.prop(system, "anisotropic_filter", text="")
         col.prop(system, "use_vertex_buffer_objects")
         #Anti-aliasing is disabled as it breaks broder/lasso select
         #col.prop(system, "use_antialiasing")
@@ -742,6 +745,7 @@ class USERPREF_PT_file(bpy.types.Panel):
 
         col.prop(paths, "save_version")
         col.prop(paths, "recent_files")
+        col.prop(paths, "use_update_recent_files_on_load")
         col.prop(paths, "use_save_preview_images")
         col.label(text="Auto Save:")
         col.prop(paths, "use_auto_save_temporary_files")
@@ -752,7 +756,7 @@ class USERPREF_PT_file(bpy.types.Panel):
 from bl_ui.space_userpref_keymap import InputKeyMapPanel
 
 
-class USERPREF_PT_input(InputKeyMapPanel):
+class USERPREF_PT_input(bpy.types.Panel, InputKeyMapPanel):
     bl_space_type = 'USER_PREFERENCES'
     bl_label = "Input"
 
@@ -800,9 +804,9 @@ class USERPREF_PT_input(InputKeyMapPanel):
 
         sub.label(text="Zoom Style:")
         sub.row().prop(inputs, "view_zoom_method", text="")
-        if inputs.view_zoom_method == 'DOLLY':
+        if inputs.view_zoom_method in {'DOLLY', 'CONTINUE'}:
             sub.row().prop(inputs, "view_zoom_axis", expand=True)
-            sub.prop(inputs, "invert_mouse_wheel_zoom")
+            sub.prop(inputs, "invert_mouse_zoom")
 
         #sub.prop(inputs, "use_mouse_mmb_paste")
 
@@ -814,12 +818,9 @@ class USERPREF_PT_input(InputKeyMapPanel):
         #sub.prop(view, "wheel_scroll_lines", text="Scroll Lines")
 
         col.separator()
-        ''' not implemented yet
         sub = col.column()
         sub.label(text="NDOF Device:")
-        sub.prop(inputs, "ndof_pan_speed", text="Pan Speed")
-        sub.prop(inputs, "ndof_rotate_speed", text="Orbit Speed")
-        '''
+        sub.prop(inputs, "ndof_sensitivity", text="NDOF Sensitivity")
 
         row.separator()
 
@@ -846,17 +847,14 @@ class USERPREF_PT_input(InputKeyMapPanel):
 
 
 class USERPREF_MT_addons_dev_guides(bpy.types.Menu):
-    bl_label = "Addons develoment guides"
+    bl_label = "Development Guides"
 
     # menu to open webpages with addons development guides
     def draw(self, context):
         layout = self.layout
-        layout.operator('wm.url_open', text='API Concepts'
-            ).url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/API/Intro'
-        layout.operator('wm.url_open', text='Addons guidelines',
-            ).url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Guidelines/Addons'
-        layout.operator('wm.url_open', text='How to share your addon',
-            ).url = 'http://wiki.blender.org/index.php/Dev:Py/Sharing'
+        layout.operator('wm.url_open', text='API Concepts', icon='URL').url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/API/Intro'
+        layout.operator('wm.url_open', text='Addon Guidelines', icon='URL').url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Guidelines/Addons'
+        layout.operator('wm.url_open', text='How to share your addon', icon='URL').url = 'http://wiki.blender.org/index.php/Dev:Py/Sharing'
 
 
 class USERPREF_PT_addons(bpy.types.Panel):
@@ -876,6 +874,29 @@ class USERPREF_PT_addons(bpy.types.Panel):
     def module_get(mod_name):
         return USERPREF_PT_addons._addons_fake_modules[mod_name]
 
+    @staticmethod
+    def is_user_addon(mod, user_addon_paths):
+        if not user_addon_paths:
+            user_script_path = bpy.utils.user_script_path()
+            if user_script_path is not None:
+                user_addon_paths.append(os.path.join(user_script_path, "addons"))
+            user_addon_paths.append(os.path.join(bpy.utils.resource_path('USER'), "scripts", "addons"))
+
+        for path in user_addon_paths:
+            if bpy.path.is_subdir(mod.__file__, path):
+                return True
+        return False
+
+    @staticmethod
+    def draw_error(layout, message):
+        lines = message.split("\n")
+        box = layout.box()
+        rowsub = box.row()
+        rowsub.label(lines[0])
+        rowsub.label(icon='ERROR')
+        for l in lines[1:]:
+            box.label(l)
+
     def draw(self, context):
         layout = self.layout
 
@@ -888,6 +909,7 @@ class USERPREF_PT_addons(bpy.types.Panel):
         split = layout.split(percentage=0.2)
         col = split.column()
         col.prop(context.window_manager, "addon_search", text="", icon='VIEWZOOM')
+        col.label(text="Categories")
         col.prop(context.window_manager, "addon_filter", expand=True)
 
         col.label(text="Supported Level")
@@ -895,10 +917,21 @@ class USERPREF_PT_addons(bpy.types.Panel):
 
         col = split.column()
 
+        # set in addon_utils.modules(...)
+        if addon_utils.error_duplicates:
+            self.draw_error(col,
+                            "Multiple addons using the same name found!\n"
+                            "likely a problem with the script search path.\n"
+                            "(see console for details)",
+                            )
+
         filter = context.window_manager.addon_filter
         search = context.window_manager.addon_search.lower()
         support = context.window_manager.addon_support
 
+        # initialized on demand
+        user_addon_paths = []
+
         for mod, info in addons:
             module_name = mod.__name__
 
@@ -968,18 +1001,21 @@ class USERPREF_PT_addons(bpy.types.Panel):
                         split = colsub.row().split(percentage=0.15)
                         split.label(text="Warning:")
                         split.label(text='  ' + info["warning"], icon='ERROR')
-                    if info["wiki_url"] or info["tracker_url"]:
+
+                    user_addon = USERPREF_PT_addons.is_user_addon(mod, user_addon_paths)
+                    tot_row = bool(info["wiki_url"]) + bool(info["tracker_url"]) + bool(user_addon)
+
+                    if tot_row:
                         split = colsub.row().split(percentage=0.15)
                         split.label(text="Internet:")
                         if info["wiki_url"]:
                             split.operator("wm.url_open", text="Link to the Wiki", icon='HELP').url = info["wiki_url"]
                         if info["tracker_url"]:
                             split.operator("wm.url_open", text="Report a Bug", icon='URL').url = info["tracker_url"]
+                        if user_addon:
+                            split.operator("wm.addon_remove", text="Remove", icon='CANCEL').module = mod.__name__
 
-                        if info["wiki_url"] and info["tracker_url"]:
-                            split.separator()
-                        else:
-                            split.separator()
+                        for i in range(4 - tot_row):
                             split.separator()
 
         # Append missing scripts
@@ -1103,7 +1139,7 @@ class WM_OT_addon_install(bpy.types.Operator):
         del pyfile_dir
         # done checking for exceptional case
 
-        contents = set(os.listdir(path_addons))
+        addons_old = {mod.__name__ for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)}
 
         #check to see if the file is in compressed format (.zip)
         if zipfile.is_zipfile(pyfile):
@@ -1115,7 +1151,7 @@ class WM_OT_addon_install(bpy.types.Operator):
 
             if self.overwrite:
                 for f in file_to_extract.namelist():
-                    __class__._module_remove(path_addons, f)
+                    WM_OT_addon_install._module_remove(path_addons, f)
             else:
                 for f in file_to_extract.namelist():
                     path_dest = os.path.join(path_addons, os.path.basename(f))
@@ -1139,7 +1175,7 @@ class WM_OT_addon_install(bpy.types.Operator):
             path_dest = os.path.join(path_addons, os.path.basename(pyfile))
 
             if self.overwrite:
-                __class__._module_remove(path_addons, os.path.basename(pyfile))
+                WM_OT_addon_install._module_remove(path_addons, os.path.basename(pyfile))
             elif os.path.exists(path_dest):
                 self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
                 return {'CANCELLED'}
@@ -1152,11 +1188,13 @@ class WM_OT_addon_install(bpy.types.Operator):
                 traceback.print_exc()
                 return {'CANCELLED'}
 
+        addons_new = {mod.__name__ for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)} - addons_old
+        addons_new.discard("modules")
+
         # disable any addons we may have enabled previously and removed.
         # this is unlikely but do just incase. bug [#23978]
-        addons_new = set(os.listdir(path_addons)) - contents
         for new_addon in addons_new:
-            addon_utils.disable(os.path.splitext(new_addon)[0])
+            addon_utils.disable(new_addon)
 
         # possible the zip contains multiple addons, we could disallow this
         # but for now just use the first
@@ -1169,6 +1207,9 @@ class WM_OT_addon_install(bpy.types.Operator):
                 context.window_manager.addon_search = info["name"]
                 break
 
+        # incase a new module path was created to install this addon.
+        bpy.utils.refresh_script_paths()
+
         # TODO, should not be a warning.
         # self.report({'WARNING'}, "File installed to '%s'\n" % path_dest)
         return {'FINISHED'}
@@ -1179,6 +1220,54 @@ class WM_OT_addon_install(bpy.types.Operator):
         return {'RUNNING_MODAL'}
 
 
+class WM_OT_addon_remove(bpy.types.Operator):
+    "Disable an addon"
+    bl_idname = "wm.addon_remove"
+    bl_label = "Remove Add-On"
+
+    module = StringProperty(name="Module", description="Module name of the addon to remove")
+
+    @staticmethod
+    def path_from_addon(module):
+        for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules):
+            if mod.__name__ == module:
+                filepath = mod.__file__
+                if os.path.exists(filepath):
+                    if os.path.splitext(os.path.basename(filepath))[0] == "__init__":
+                        return os.path.dirname(filepath), True
+                    else:
+                        return filepath, False
+        return None, False
+
+    def execute(self, context):
+        path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
+        if path is None:
+            self.report('WARNING', "Addon path %r could not be found" % path)
+            return {'CANCELLED'}
+
+        # incase its enabled
+        addon_utils.disable(self.module)
+
+        import shutil
+        if isdir:
+            shutil.rmtree(path)
+        else:
+            os.remove(path)
+
+        context.area.tag_redraw()
+        return {'FINISHED'}
+
+    # lame confirmation check
+    def draw(self, context):
+        self.layout.label(text="Remove Addon: %r?" % self.module)
+        path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
+        self.layout.label(text="Path: %r" % path)
+
+    def invoke(self, context, event):
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self, width=600)
+
+
 class WM_OT_addon_expand(bpy.types.Operator):
     "Display more information on this add-on"
     bl_idname = "wm.addon_expand"