Merged changes in the trunk up to revision 35203.
[blender.git] / release / scripts / ui / space_userpref.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 import bpy
21 import os
22 import shutil
23 import addon_utils
24
25 from bpy.props import StringProperty, BoolProperty, EnumProperty
26
27
28 def ui_items_general(col, context):
29     """ General UI Theme Settings (User Interface)
30     """
31
32     row = col.row()
33
34     subsplit = row.split(percentage=0.95)
35
36     padding = subsplit.split(percentage=0.15)
37     colsub = padding.column()
38     colsub = padding.column()
39     colsub.row().prop(context, "outline")
40     colsub.row().prop(context, "item", slider=True)
41     colsub.row().prop(context, "inner", slider=True)
42     colsub.row().prop(context, "inner_sel", slider=True)
43
44     subsplit = row.split(percentage=0.85)
45
46     padding = subsplit.split(percentage=0.15)
47     colsub = padding.column()
48     colsub = padding.column()
49     colsub.row().prop(context, "text")
50     colsub.row().prop(context, "text_sel")
51     colsub.prop(context, "show_shaded")
52     subsub = colsub.column(align=True)
53     subsub.active = context.show_shaded
54     subsub.prop(context, "shadetop")
55     subsub.prop(context, "shadedown")
56
57     col.separator()
58
59
60 def opengl_lamp_buttons(column, lamp):
61     split = column.split(percentage=0.1)
62
63     split.prop(lamp, "use", text="", icon='OUTLINER_OB_LAMP' if lamp.use else 'LAMP_DATA')
64
65     col = split.column()
66     col.active = lamp.use
67     row = col.row()
68     row.label(text="Diffuse:")
69     row.prop(lamp, "diffuse_color", text="")
70     row = col.row()
71     row.label(text="Specular:")
72     row.prop(lamp, "specular_color", text="")
73
74     col = split.column()
75     col.active = lamp.use
76     col.prop(lamp, "direction", text="")
77
78
79 class USERPREF_HT_header(bpy.types.Header):
80     bl_space_type = 'USER_PREFERENCES'
81
82     def draw(self, context):
83         layout = self.layout
84         layout.template_header(menus=False)
85
86         userpref = context.user_preferences
87
88         layout.operator_context = 'EXEC_AREA'
89         layout.operator("wm.save_homefile", text="Save As Default")
90
91         layout.operator_context = 'INVOKE_DEFAULT'
92
93         if userpref.active_section == 'INPUT':
94             layout.operator("wm.keyconfig_export")
95             layout.operator("wm.keyconfig_import")
96         elif userpref.active_section == 'ADDONS':
97             layout.operator("wm.addon_install")
98             layout.menu("USERPREF_MT_addons_dev_guides", text="  Addons Developer Guides", icon='INFO')
99         elif userpref.active_section == 'THEMES':
100             layout.operator("ui.reset_default_theme")
101
102
103 class USERPREF_PT_tabs(bpy.types.Panel):
104     bl_label = ""
105     bl_space_type = 'USER_PREFERENCES'
106     bl_region_type = 'WINDOW'
107     bl_options = {'HIDE_HEADER'}
108
109     def draw(self, context):
110         layout = self.layout
111
112         userpref = context.user_preferences
113
114         layout.prop(userpref, "active_section", expand=True)
115
116
117 class USERPREF_MT_interaction_presets(bpy.types.Menu):
118     bl_label = "Presets"
119     preset_subdir = "interaction"
120     preset_operator = "script.execute_preset"
121     draw = bpy.types.Menu.draw_preset
122
123
124 class USERPREF_MT_splash(bpy.types.Menu):
125     bl_label = "Splash"
126
127     def draw(self, context):
128         layout = self.layout
129         split = layout.split()
130         row = split.row()
131         row.label("")
132         row = split.row()
133         row.label("Interaction:")
134         # XXX, no redraws
135         # text = bpy.path.display_name(context.window_manager.keyconfigs.active.name)
136         # if not text:
137         #     text = "Blender (default)"
138         row.menu("USERPREF_MT_keyconfigs", text="Preset")
139
140
141 class USERPREF_PT_interface(bpy.types.Panel):
142     bl_space_type = 'USER_PREFERENCES'
143     bl_label = "Interface"
144     bl_region_type = 'WINDOW'
145     bl_options = {'HIDE_HEADER'}
146
147     @classmethod
148     def poll(cls, context):
149         userpref = context.user_preferences
150         return (userpref.active_section == 'INTERFACE')
151
152     def draw(self, context):
153         layout = self.layout
154
155         userpref = context.user_preferences
156         view = userpref.view
157
158         row = layout.row()
159
160         col = row.column()
161         col.label(text="Display:")
162         col.prop(view, "show_tooltips")
163         col.prop(view, "show_tooltips_python")
164         col.prop(view, "show_object_info", text="Object Info")
165         col.prop(view, "show_large_cursors")
166         col.prop(view, "show_view_name", text="View Name")
167         col.prop(view, "show_playback_fps", text="Playback FPS")
168         col.prop(view, "use_global_scene")
169         col.prop(view, "object_origin_size")
170
171         col.separator()
172         col.separator()
173         col.separator()
174
175         col.prop(view, "show_mini_axis", text="Display Mini Axis")
176         sub = col.column()
177         sub.active = view.show_mini_axis
178         sub.prop(view, "mini_axis_size", text="Size")
179         sub.prop(view, "mini_axis_brightness", text="Brightness")
180
181         col.separator()
182         row.separator()
183         row.separator()
184
185         col = row.column()
186         col.label(text="View Manipulation:")
187         col.prop(view, "use_mouse_auto_depth")
188         col.prop(view, "use_zoom_to_mouse")
189         col.prop(view, "use_rotate_around_active")
190         col.prop(view, "use_global_pivot")
191
192         col.separator()
193
194         col.prop(view, "use_auto_perspective")
195         col.prop(view, "smooth_view")
196         col.prop(view, "rotation_angle")
197         
198         col.separator()
199         col.separator()
200         
201         col.label(text="2D Viewports:")
202         col.prop(view, "view2d_grid_minimum_spacing", text="Minimum Grid Spacing")
203         col.prop(view, "timecode_style")
204         
205         col.separator()
206         col.separator()
207
208         col.label(text="2D Viewports:")
209         col.prop(view, "view2d_grid_spacing_min", text="Minimum Grid Spacing")
210         col.prop(view, "timecode_style")
211
212         row.separator()
213         row.separator()
214
215         col = row.column()
216         #Toolbox doesn't exist yet
217         #col.label(text="Toolbox:")
218         #col.prop(view, "show_column_layout")
219         #col.label(text="Open Toolbox Delay:")
220         #col.prop(view, "open_left_mouse_delay", text="Hold LMB")
221         #col.prop(view, "open_right_mouse_delay", text="Hold RMB")
222         col.prop(view, "show_manipulator")
223         sub = col.column()
224         sub.active = view.show_manipulator
225         sub.prop(view, "manipulator_size", text="Size")
226         sub.prop(view, "manipulator_handle_size", text="Handle Size")
227         sub.prop(view, "manipulator_hotspot", text="Hotspot")
228
229         col.separator()
230         col.separator()
231         col.separator()
232
233         col.label(text="Menus:")
234         col.prop(view, "use_mouse_over_open")
235         col.label(text="Menu Open Delay:")
236         col.prop(view, "open_toplevel_delay", text="Top Level")
237         col.prop(view, "open_sublevel_delay", text="Sub Level")
238
239         col.separator()
240
241         col.prop(view, "show_splash")
242
243
244 class USERPREF_PT_edit(bpy.types.Panel):
245     bl_space_type = 'USER_PREFERENCES'
246     bl_label = "Edit"
247     bl_region_type = 'WINDOW'
248     bl_options = {'HIDE_HEADER'}
249
250     @classmethod
251     def poll(cls, context):
252         userpref = context.user_preferences
253         return (userpref.active_section == 'EDITING')
254
255     def draw(self, context):
256         layout = self.layout
257
258         userpref = context.user_preferences
259         edit = userpref.edit
260
261         row = layout.row()
262
263         col = row.column()
264         col.label(text="Link Materials To:")
265         col.prop(edit, "material_link", text="")
266
267         col.separator()
268         col.separator()
269         col.separator()
270
271         col.label(text="New Objects:")
272         col.prop(edit, "use_enter_edit_mode")
273         col.label(text="Align To:")
274         col.prop(edit, "object_align", text="")
275
276         col.separator()
277         col.separator()
278         col.separator()
279
280         col.label(text="Undo:")
281         col.prop(edit, "use_global_undo")
282         col.prop(edit, "undo_steps", text="Steps")
283         col.prop(edit, "undo_memory_limit", text="Memory Limit")
284
285         row.separator()
286         row.separator()
287
288         col = row.column()
289         col.label(text="Grease Pencil:")
290         col.prop(edit, "grease_pencil_manhattan_distance", text="Manhattan Distance")
291         col.prop(edit, "grease_pencil_euclidean_distance", text="Euclidean Distance")
292         #col.prop(edit, "use_grease_pencil_simplify_stroke", text="Simplify Stroke")
293         col.prop(edit, "grease_pencil_eraser_radius", text="Eraser Radius")
294         col.prop(edit, "use_grease_pencil_smooth_stroke", text="Smooth Stroke")
295         col.separator()
296         col.separator()
297         col.separator()
298         col.label(text="Playback:")
299         col.prop(edit, "use_negative_frames")
300
301         row.separator()
302         row.separator()
303
304         col = row.column()
305         col.label(text="Keyframing:")
306         col.prop(edit, "use_visual_keying")
307         col.prop(edit, "use_keyframe_insert_needed", text="Only Insert Needed")
308
309         col.separator()
310
311         col.prop(edit, "use_auto_keying", text="Auto Keyframing:")
312
313         sub = col.column()
314
315         # sub.active = edit.use_keyframe_insert_auto # incorrect, timeline can enable
316         sub.prop(edit, "use_keyframe_insert_available", text="Only Insert Available")
317
318         col.separator()
319
320         col.label(text="New F-Curve Defaults:")
321         col.prop(edit, "keyframe_new_interpolation_type", text="Interpolation")
322         col.prop(edit, "keyframe_new_handle_type", text="Handles")
323         col.prop(edit, "use_insertkey_xyz_to_rgb", text="XYZ to RGB")
324
325         col.separator()
326         col.separator()
327         col.separator()
328
329         col.label(text="Transform:")
330         col.prop(edit, "use_drag_immediately")
331
332         row.separator()
333         row.separator()
334
335         col = row.column()
336         col.prop(edit, "sculpt_paint_overlay_color", text="Sculpt Overlay Color")
337
338         col.separator()
339         col.separator()
340         col.separator()
341
342         col.label(text="Duplicate Data:")
343         col.prop(edit, "use_duplicate_mesh", text="Mesh")
344         col.prop(edit, "use_duplicate_surface", text="Surface")
345         col.prop(edit, "use_duplicate_curve", text="Curve")
346         col.prop(edit, "use_duplicate_text", text="Text")
347         col.prop(edit, "use_duplicate_metaball", text="Metaball")
348         col.prop(edit, "use_duplicate_armature", text="Armature")
349         col.prop(edit, "use_duplicate_lamp", text="Lamp")
350         col.prop(edit, "use_duplicate_material", text="Material")
351         col.prop(edit, "use_duplicate_texture", text="Texture")
352         #col.prop(edit, "use_duplicate_fcurve", text="F-Curve")
353         col.prop(edit, "use_duplicate_action", text="Action")
354         col.prop(edit, "use_duplicate_particle", text="Particle")
355
356
357 class USERPREF_PT_system(bpy.types.Panel):
358     bl_space_type = 'USER_PREFERENCES'
359     bl_label = "System"
360     bl_region_type = 'WINDOW'
361     bl_options = {'HIDE_HEADER'}
362
363     @classmethod
364     def poll(cls, context):
365         userpref = context.user_preferences
366         return (userpref.active_section == 'SYSTEM')
367
368     def draw(self, context):
369         layout = self.layout
370
371         userpref = context.user_preferences
372         system = userpref.system
373
374         split = layout.split()
375
376         # 1. Column
377         column = split.column()
378         colsplit = column.split(percentage=0.85)
379
380         col = colsplit.column()
381         col.label(text="General:")
382         col.prop(system, "dpi")
383         col.prop(system, "frame_server_port")
384         col.prop(system, "scrollback", text="Console Scrollback")
385         col.prop(system, "author", text="Author")
386         col.prop(system, "use_scripts_auto_execute")
387         col.prop(system, "use_tabs_as_spaces")
388
389         col.separator()
390         col.separator()
391         col.separator()
392
393         col.label(text="Sound:")
394         col.row().prop(system, "audio_device", expand=True)
395         sub = col.column()
396         sub.active = system.audio_device != 'NONE'
397         #sub.prop(system, "use_preview_images")
398         sub.prop(system, "audio_channels", text="Channels")
399         sub.prop(system, "audio_mixing_buffer", text="Mixing Buffer")
400         sub.prop(system, "audio_sample_rate", text="Sample Rate")
401         sub.prop(system, "audio_sample_format", text="Sample Format")
402
403         col.separator()
404         col.separator()
405         col.separator()
406
407         col.label(text="Screencast:")
408         col.prop(system, "screencast_fps")
409         col.prop(system, "screencast_wait_time")
410         col.separator()
411         col.separator()
412         col.separator()
413
414         #column = split.column()
415         #colsplit = column.split(percentage=0.85)
416
417         # No translation in 2.5 yet
418         #col.prop(system, "language")
419         #col.label(text="Translate:")
420         #col.prop(system, "use_translate_tooltips", text="Tooltips")
421         #col.prop(system, "use_translate_buttons", text="Labels")
422         #col.prop(system, "use_translate_toolbox", text="Toolbox")
423
424         #col.separator()
425
426         #col.prop(system, "use_textured_fonts")
427
428         # 2. Column
429         column = split.column()
430         colsplit = column.split(percentage=0.85)
431
432         col = colsplit.column()
433         col.label(text="OpenGL:")
434         col.prop(system, "gl_clip_alpha", slider=True)
435         col.prop(system, "use_mipmaps")
436         col.prop(system, "use_vertex_buffer_objects")
437         #Anti-aliasing is disabled as it breaks broder/lasso select
438         #col.prop(system, "use_antialiasing")
439         col.label(text="Window Draw Method:")
440         col.prop(system, "window_draw_method", text="")
441         col.label(text="Text Draw Options:")
442         col.prop(system, "use_text_antialiasing")
443         col.label(text="Textures:")
444         col.prop(system, "gl_texture_limit", text="Limit Size")
445         col.prop(system, "texture_time_out", text="Time Out")
446         col.prop(system, "texture_collection_rate", text="Collection Rate")
447
448         col.separator()
449         col.separator()
450         col.separator()
451
452         col.label(text="Sequencer:")
453         col.prop(system, "prefetch_frames")
454         col.prop(system, "memory_cache_limit")
455
456         # 3. Column
457         column = split.column()
458
459         column.label(text="Solid OpenGL lights:")
460
461         split = column.split(percentage=0.1)
462         split.label()
463         split.label(text="Colors:")
464         split.label(text="Direction:")
465
466         lamp = system.solid_lights[0]
467         opengl_lamp_buttons(column, lamp)
468
469         lamp = system.solid_lights[1]
470         opengl_lamp_buttons(column, lamp)
471
472         lamp = system.solid_lights[2]
473         opengl_lamp_buttons(column, lamp)
474
475         column.separator()
476         column.separator()
477         column.separator()
478
479         column.label(text="Color Picker Type:")
480         column.row().prop(system, "color_picker_type", text="")
481
482         column.separator()
483         column.separator()
484         column.separator()
485
486         column.prop(system, "use_weight_color_range", text="Custom Weight Paint Range")
487         sub = column.column()
488         sub.active = system.use_weight_color_range
489         sub.template_color_ramp(system, "weight_color_range", expand=True)
490
491
492 class USERPREF_PT_theme(bpy.types.Panel):
493     bl_space_type = 'USER_PREFERENCES'
494     bl_label = "Themes"
495     bl_region_type = 'WINDOW'
496     bl_options = {'HIDE_HEADER'}
497
498     @staticmethod
499     def _theme_generic(split, themedata):
500
501         row = split.row()
502
503         subsplit = row.split(percentage=0.95)
504
505         padding1 = subsplit.split(percentage=0.15)
506         padding1.column()
507
508         subsplit = row.split(percentage=0.85)
509
510         padding2 = subsplit.split(percentage=0.15)
511         padding2.column()
512
513         colsub_pair = padding1.column(), padding2.column()
514
515         props_type = {}
516
517         for i, prop in enumerate(themedata.rna_type.properties):
518             attr = prop.identifier
519             if attr == "rna_type":
520                 continue
521
522             props_type.setdefault((prop.type, prop.subtype), []).append(prop.identifier)
523
524         for props_type, props_ls in sorted(props_type.items()):
525             for i, attr in enumerate(props_ls):
526                 colsub_pair[i % 2].row().prop(themedata, attr)
527
528     @classmethod
529     def poll(cls, context):
530         userpref = context.user_preferences
531         return (userpref.active_section == 'THEMES')
532
533     def draw(self, context):
534         layout = self.layout
535
536         theme = context.user_preferences.themes[0]
537
538         split_themes = layout.split(percentage=0.2)
539         split_themes.prop(theme, "theme_area", expand=True)
540
541         split = layout.split(percentage=0.4)
542
543         layout.separator()
544         layout.separator()
545
546         split = split_themes.split()
547
548         if theme.theme_area == 'USER_INTERFACE':
549             col = split.column()
550
551             ui = theme.user_interface.wcol_regular
552             col.label(text="Regular:")
553             ui_items_general(col, ui)
554
555             ui = theme.user_interface.wcol_tool
556             col.label(text="Tool:")
557             ui_items_general(col, ui)
558
559             ui = theme.user_interface.wcol_radio
560             col.label(text="Radio Buttons:")
561             ui_items_general(col, ui)
562
563             ui = theme.user_interface.wcol_text
564             col.label(text="Text:")
565             ui_items_general(col, ui)
566
567             ui = theme.user_interface.wcol_option
568             col.label(text="Option:")
569             ui_items_general(col, ui)
570
571             ui = theme.user_interface.wcol_toggle
572             col.label(text="Toggle:")
573             ui_items_general(col, ui)
574
575             ui = theme.user_interface.wcol_num
576             col.label(text="Number Field:")
577             ui_items_general(col, ui)
578
579             ui = theme.user_interface.wcol_numslider
580             col.label(text="Value Slider:")
581             ui_items_general(col, ui)
582
583             ui = theme.user_interface.wcol_box
584             col.label(text="Box:")
585             ui_items_general(col, ui)
586
587             ui = theme.user_interface.wcol_menu
588             col.label(text="Menu:")
589             ui_items_general(col, ui)
590
591             ui = theme.user_interface.wcol_pulldown
592             col.label(text="Pulldown:")
593             ui_items_general(col, ui)
594
595             ui = theme.user_interface.wcol_menu_back
596             col.label(text="Menu Back:")
597             ui_items_general(col, ui)
598
599             ui = theme.user_interface.wcol_menu_item
600             col.label(text="Menu Item:")
601             ui_items_general(col, ui)
602
603             ui = theme.user_interface.wcol_scroll
604             col.label(text="Scroll Bar:")
605             ui_items_general(col, ui)
606
607             ui = theme.user_interface.wcol_progress
608             col.label(text="Progress Bar:")
609             ui_items_general(col, ui)
610
611             ui = theme.user_interface.wcol_list_item
612             col.label(text="List Item:")
613             ui_items_general(col, ui)
614
615             ui = theme.user_interface.wcol_state
616             col.label(text="State:")
617
618             row = col.row()
619
620             subsplit = row.split(percentage=0.95)
621
622             padding = subsplit.split(percentage=0.15)
623             colsub = padding.column()
624             colsub = padding.column()
625             colsub.row().prop(ui, "inner_anim")
626             colsub.row().prop(ui, "inner_anim_sel")
627             colsub.row().prop(ui, "inner_driven")
628             colsub.row().prop(ui, "inner_driven_sel")
629
630             subsplit = row.split(percentage=0.85)
631
632             padding = subsplit.split(percentage=0.15)
633             colsub = padding.column()
634             colsub = padding.column()
635             colsub.row().prop(ui, "inner_key")
636             colsub.row().prop(ui, "inner_key_sel")
637             colsub.row().prop(ui, "blend")
638
639             ui = theme.user_interface
640             col.separator()
641             col.separator()
642
643             split = col.split(percentage=0.93)
644             split.prop(ui, "icon_file")
645
646             layout.separator()
647             layout.separator()
648         elif theme.theme_area == 'COLOR_SETS':
649             col = split.column()
650
651             for i, ui in enumerate(theme.bone_color_sets):
652                 col.label(text="Color Set %d:" % (i + 1))  # i starts from 0
653
654                 row = col.row()
655
656                 subsplit = row.split(percentage=0.95)
657
658                 padding = subsplit.split(percentage=0.15)
659                 colsub = padding.column()
660                 colsub = padding.column()
661                 colsub.row().prop(ui, "normal")
662                 colsub.row().prop(ui, "select")
663                 colsub.row().prop(ui, "active")
664
665                 subsplit = row.split(percentage=0.85)
666
667                 padding = subsplit.split(percentage=0.15)
668                 colsub = padding.column()
669                 colsub = padding.column()
670                 colsub.row().prop(ui, "show_colored_constraints")
671         else:
672             self._theme_generic(split, getattr(theme, theme.theme_area.lower()))
673
674
675 class USERPREF_PT_file(bpy.types.Panel):
676     bl_space_type = 'USER_PREFERENCES'
677     bl_label = "Files"
678     bl_region_type = 'WINDOW'
679     bl_options = {'HIDE_HEADER'}
680
681     @classmethod
682     def poll(cls, context):
683         userpref = context.user_preferences
684         return (userpref.active_section == 'FILES')
685
686     def draw(self, context):
687         layout = self.layout
688
689         userpref = context.user_preferences
690         paths = userpref.filepaths
691
692         split = layout.split(percentage=0.7)
693
694         col = split.column()
695         col.label(text="File Paths:")
696
697         colsplit = col.split(percentage=0.95)
698         col1 = colsplit.split(percentage=0.3)
699
700         sub = col1.column()
701         sub.label(text="Fonts:")
702         sub.label(text="Textures:")
703         sub.label(text="Texture Plugins:")
704         sub.label(text="Sequence Plugins:")
705         sub.label(text="Render Output:")
706         sub.label(text="Scripts:")
707         sub.label(text="Sounds:")
708         sub.label(text="Temp:")
709         sub.label(text="Image Editor:")
710         sub.label(text="Animation Player:")
711
712         sub = col1.column()
713         sub.prop(paths, "font_directory", text="")
714         sub.prop(paths, "texture_directory", text="")
715         sub.prop(paths, "texture_plugin_directory", text="")
716         sub.prop(paths, "sequence_plugin_directory", text="")
717         sub.prop(paths, "render_output_directory", text="")
718         sub.prop(paths, "script_directory", text="")
719         sub.prop(paths, "sound_directory", text="")
720         sub.prop(paths, "temporary_directory", text="")
721         sub.prop(paths, "image_editor", text="")
722         subsplit = sub.split(percentage=0.3)
723         subsplit.prop(paths, "animation_player_preset", text="")
724         subsplit.prop(paths, "animation_player", text="")
725
726         col = split.column()
727         col.label(text="Save & Load:")
728         col.prop(paths, "use_relative_paths")
729         col.prop(paths, "use_file_compression")
730         col.prop(paths, "use_load_ui")
731         col.prop(paths, "use_filter_files")
732         col.prop(paths, "show_hidden_files_datablocks")
733         col.prop(paths, "hide_recent_locations")
734         col.prop(paths, "show_thumbnails")
735
736         col.separator()
737         col.separator()
738
739         col.prop(paths, "save_version")
740         col.prop(paths, "recent_files")
741         col.prop(paths, "use_save_preview_images")
742         col.label(text="Auto Save:")
743         col.prop(paths, "use_auto_save_temporary_files")
744         sub = col.column()
745         sub.active = paths.use_auto_save_temporary_files
746         sub.prop(paths, "auto_save_time", text="Timer (mins)")
747
748 from space_userpref_keymap import InputKeyMapPanel
749
750
751 class USERPREF_PT_input(InputKeyMapPanel):
752     bl_space_type = 'USER_PREFERENCES'
753     bl_label = "Input"
754
755     @classmethod
756     def poll(cls, context):
757         userpref = context.user_preferences
758         return (userpref.active_section == 'INPUT')
759
760     def draw_input_prefs(self, inputs, layout):
761         # General settings
762         row = layout.row()
763         col = row.column()
764
765         sub = col.column()
766         sub.label(text="Presets:")
767         subrow = sub.row(align=True)
768
769         subrow.menu("USERPREF_MT_interaction_presets", text=bpy.types.USERPREF_MT_interaction_presets.bl_label)
770         subrow.operator("wm.interaction_preset_add", text="", icon='ZOOMIN')
771         subrow.operator("wm.interaction_preset_add", text="", icon='ZOOMOUT').remove_active = True
772         sub.separator()
773
774         sub.label(text="Mouse:")
775         sub1 = sub.column()
776         sub1.active = (inputs.select_mouse == 'RIGHT')
777         sub1.prop(inputs, "use_mouse_emulate_3_button")
778         sub.prop(inputs, "use_mouse_continuous")
779         sub.prop(inputs, "drag_threshold")
780
781         sub.label(text="Select With:")
782         sub.row().prop(inputs, "select_mouse", expand=True)
783
784         sub = col.column()
785         sub.label(text="Double Click:")
786         sub.prop(inputs, "mouse_double_click_time", text="Speed")
787
788         sub.separator()
789
790         sub.prop(inputs, "use_emulate_numpad")
791
792         sub.separator()
793
794         sub.label(text="Orbit Style:")
795         sub.row().prop(inputs, "view_rotate_method", expand=True)
796
797         sub.label(text="Zoom Style:")
798         sub.row().prop(inputs, "view_zoom_method", text="")
799         if inputs.view_zoom_method == 'DOLLY':
800             sub.row().prop(inputs, "view_zoom_axis", expand=True)
801             sub.prop(inputs, "invert_mouse_wheel_zoom")
802
803         #sub.prop(inputs, "use_mouse_mmb_paste")
804
805         #col.separator()
806
807         sub = col.column()
808         sub.label(text="Mouse Wheel:")
809         sub.prop(inputs, "invert_zoom_wheel", text="Invert Wheel Zoom Direction")
810         #sub.prop(view, "wheel_scroll_lines", text="Scroll Lines")
811
812         col.separator()
813         ''' not implemented yet
814         sub = col.column()
815         sub.label(text="NDOF Device:")
816         sub.prop(inputs, "ndof_pan_speed", text="Pan Speed")
817         sub.prop(inputs, "ndof_rotate_speed", text="Orbit Speed")
818         '''
819
820         row.separator()
821
822     def draw(self, context):
823         layout = self.layout
824
825         #import time
826
827         #start = time.time()
828
829         userpref = context.user_preferences
830
831         inputs = userpref.inputs
832
833         split = layout.split(percentage=0.25)
834
835         # Input settings
836         self.draw_input_prefs(inputs, split)
837
838         # Keymap Settings
839         self.draw_keymaps(context, split)
840
841         #print("runtime", time.time() - start)
842
843
844 class USERPREF_MT_addons_dev_guides(bpy.types.Menu):
845     bl_label = "Addons develoment guides"
846
847     # menu to open webpages with addons development guides
848     def draw(self, context):
849         layout = self.layout
850         layout.operator('wm.url_open', text='API Concepts'
851             ).url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/API/Intro'
852         layout.operator('wm.url_open', text='Addons guidelines',
853             ).url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Guidelines/Addons'
854         layout.operator('wm.url_open', text='How to share your addon',
855             ).url = 'http://wiki.blender.org/index.php/Dev:Py/Sharing'
856
857
858 class USERPREF_PT_addons(bpy.types.Panel):
859     bl_space_type = 'USER_PREFERENCES'
860     bl_label = "Addons"
861     bl_region_type = 'WINDOW'
862     bl_options = {'HIDE_HEADER'}
863
864     _addons_cats = None
865     _addons_sups = None
866     _addons_fake_modules = {}
867
868     @classmethod
869     def poll(cls, context):
870         userpref = context.user_preferences
871         return (userpref.active_section == 'ADDONS')
872
873     @staticmethod
874     def module_get(mod_name):
875         return USERPREF_PT_addons._addons_fake_modules[mod_name]
876
877     def draw(self, context):
878         layout = self.layout
879
880         userpref = context.user_preferences
881         used_ext = {ext.module for ext in userpref.addons}
882
883         # collect the categories that can be filtered on
884         addons = [(mod, addon_utils.module_bl_info(mod)) for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)]
885
886         cats = {info["category"] for mod, info in addons}
887         cats.discard("")
888
889         if USERPREF_PT_addons._addons_cats != cats:
890             bpy.types.WindowManager.addon_filter = EnumProperty(items=[(cat, cat, "") for cat in ["All", "Enabled", "Disabled"] + sorted(cats)], name="Category", description="Filter add-ons by category")
891             bpy.types.WindowManager.addon_search = StringProperty(name="Search", description="Search within the selected filter")
892             USERPREF_PT_addons._addons_cats = cats
893
894         sups_default = {'OFFICIAL', 'COMMUNITY'}
895         sups = sups_default | {info["support"] for mod, info in addons}
896         sups.discard("")
897
898         if USERPREF_PT_addons._addons_sups != sups:
899             bpy.types.WindowManager.addon_support = EnumProperty(items=[(sup, sup.title(), "") for  sup in reversed(sorted(sups))], name="Support", description="Display support level", default=sups_default, options={'ENUM_FLAG'})
900             USERPREF_PT_addons._addons_sups = sups
901
902         split = layout.split(percentage=0.2)
903         col = split.column()
904         col.prop(context.window_manager, "addon_search", text="", icon='VIEWZOOM')
905         col.prop(context.window_manager, "addon_filter", expand=True)
906
907         col.label(text="Supported Level")
908         col.prop(context.window_manager, "addon_support", expand=True)
909
910         col = split.column()
911
912         filter = context.window_manager.addon_filter
913         search = context.window_manager.addon_search.lower()
914         support = context.window_manager.addon_support
915
916         for mod, info in addons:
917             module_name = mod.__name__
918
919             is_enabled = module_name in used_ext
920
921             if info["support"] not in support:
922                 continue
923
924             # check if add-on should be visible with current filters
925             if (filter == "All") or \
926                     (filter == info["category"]) or \
927                     (filter == "Enabled" and is_enabled) or \
928                     (filter == "Disabled" and not is_enabled):
929
930                 if search and search not in info["name"].lower():
931                     if info["author"]:
932                         if search not in info["author"].lower():
933                             continue
934                     else:
935                         continue
936
937                 # Addon UI Code
938                 box = col.column().box()
939                 colsub = box.column()
940                 row = colsub.row()
941
942                 row.operator("wm.addon_expand", icon='TRIA_DOWN' if info["show_expanded"] else 'TRIA_RIGHT', emboss=False).module = module_name
943
944                 rowsub = row.row()
945                 rowsub.active = is_enabled
946                 rowsub.label(text='%s: %s' % (info['category'], info["name"]))
947                 if info["warning"]:
948                     rowsub.label(icon='ERROR')
949
950                 # icon showing support level.
951                 if info["support"] == 'OFFICIAL':
952                     rowsub.label(icon='FILE_BLEND')
953                 elif info["support"] == 'COMMUNITY':
954                     rowsub.label(icon='POSE_DATA')
955                 else:
956                     rowsub.label(icon='QUESTION')
957
958                 if is_enabled:
959                     row.operator("wm.addon_disable", icon='CHECKBOX_HLT', text="", emboss=False).module = module_name
960                 else:
961                     row.operator("wm.addon_enable", icon='CHECKBOX_DEHLT', text="", emboss=False).module = module_name
962
963                 # Expanded UI (only if additional infos are available)
964                 if info["show_expanded"]:
965                     if info["description"]:
966                         split = colsub.row().split(percentage=0.15)
967                         split.label(text='Description:')
968                         split.label(text=info["description"])
969                     if info["location"]:
970                         split = colsub.row().split(percentage=0.15)
971                         split.label(text='Location:')
972                         split.label(text=info["location"])
973                     if info["author"]:
974                         split = colsub.row().split(percentage=0.15)
975                         split.label(text='Author:')
976                         split.label(text=info["author"])
977                     if info["version"]:
978                         split = colsub.row().split(percentage=0.15)
979                         split.label(text='Version:')
980                         split.label(text='.'.join(str(x) for x in info["version"]))
981                     if info["warning"]:
982                         split = colsub.row().split(percentage=0.15)
983                         split.label(text="Warning:")
984                         split.label(text='  ' + info["warning"], icon='ERROR')
985                     if info["wiki_url"] or info["tracker_url"]:
986                         split = colsub.row().split(percentage=0.15)
987                         split.label(text="Internet:")
988                         if info["wiki_url"]:
989                             split.operator("wm.url_open", text="Link to the Wiki", icon='HELP').url = info["wiki_url"]
990                         if info["tracker_url"]:
991                             split.operator("wm.url_open", text="Report a Bug", icon='URL').url = info["tracker_url"]
992
993                         if info["wiki_url"] and info["tracker_url"]:
994                             split.separator()
995                         else:
996                             split.separator()
997                             split.separator()
998
999         # Append missing scripts
1000         # First collect scripts that are used but have no script file.
1001         module_names = {mod.__name__ for mod, info in addons}
1002         missing_modules = {ext for ext in used_ext if ext not in module_names}
1003
1004         if missing_modules and filter in ("All", "Enabled"):
1005             col.column().separator()
1006             col.column().label(text="Missing script files")
1007
1008             module_names = {mod.__name__ for mod, info in addons}
1009             for module_name in sorted(missing_modules):
1010                 is_enabled = module_name in used_ext
1011                 # Addon UI Code
1012                 box = col.column().box()
1013                 colsub = box.column()
1014                 row = colsub.row()
1015
1016                 row.label(text=module_name, icon='ERROR')
1017
1018                 if is_enabled:
1019                     row.operator("wm.addon_disable", icon='CHECKBOX_HLT', text="", emboss=False).module = module_name
1020
1021
1022 class WM_OT_addon_enable(bpy.types.Operator):
1023     "Enable an addon"
1024     bl_idname = "wm.addon_enable"
1025     bl_label = "Enable Add-On"
1026
1027     module = StringProperty(name="Module", description="Module name of the addon to enable")
1028
1029     def execute(self, context):
1030         mod = addon_utils.enable(self.module)
1031
1032         if mod:
1033             # check if add-on is written for current blender version, or raise a warning
1034             info = addon_utils.module_bl_info(mod)
1035
1036             if info.get("blender", (0, 0, 0)) > bpy.app.version:
1037                 self.report("WARNING','This script was written for a newer version of Blender and might not function (correctly).\nThe script is enabled though.")
1038             return {'FINISHED'}
1039         else:
1040             return {'CANCELLED'}
1041
1042
1043 class WM_OT_addon_disable(bpy.types.Operator):
1044     "Disable an addon"
1045     bl_idname = "wm.addon_disable"
1046     bl_label = "Disable Add-On"
1047
1048     module = StringProperty(name="Module", description="Module name of the addon to disable")
1049
1050     def execute(self, context):
1051         addon_utils.disable(self.module)
1052         return {'FINISHED'}
1053
1054
1055 class WM_OT_addon_install(bpy.types.Operator):
1056     "Install an addon"
1057     bl_idname = "wm.addon_install"
1058     bl_label = "Install Add-On..."
1059
1060     overwrite = BoolProperty(name="Overwrite", description="Remove existing addons with the same ID", default=True)
1061
1062     filepath = StringProperty(name="File Path", description="File path to write file to")
1063     filter_folder = BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
1064     filter_python = BoolProperty(name="Filter python", description="", default=True, options={'HIDDEN'})
1065     filter_glob = StringProperty(default="*.py;*.zip", options={'HIDDEN'})
1066
1067     @staticmethod
1068     def _module_remove(path_addons, module):
1069         module = os.path.splitext(module)[0]
1070         for f in os.listdir(path_addons):
1071             f_base = os.path.splitext(f)[0]
1072             if f_base == module:
1073                 f_full = os.path.join(path_addons, f)
1074
1075                 if os.path.isdir(f_full):
1076                     os.rmdir(f_full)
1077                 else:
1078                     os.remove(f_full)
1079
1080     def execute(self, context):
1081         import traceback
1082         import zipfile
1083         pyfile = self.filepath
1084
1085         # dont use bpy.utils.script_paths("addons") because we may not be able to write to it.
1086         path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True)
1087
1088         if not path_addons:
1089             self.report({'ERROR'}, "Failed to get addons path")
1090             return {'CANCELLED'}
1091
1092         # Check if we are installing from a target path,
1093         # doing so causes 2+ addons of same name or when the same from/to
1094         # location is used, removal of the file!
1095         addon_path = ""
1096         pyfile_dir = os.path.dirname(pyfile)
1097         for addon_path in addon_utils.paths():
1098             if os.path.samefile(pyfile_dir, addon_path):
1099                 self.report({'ERROR'}, "Source file is in the addon search path: %r" % addon_path)
1100                 return {'CANCELLED'}
1101         del addon_path
1102         del pyfile_dir
1103         # done checking for exceptional case
1104
1105         contents = set(os.listdir(path_addons))
1106
1107         #check to see if the file is in compressed format (.zip)
1108         if zipfile.is_zipfile(pyfile):
1109             try:
1110                 file_to_extract = zipfile.ZipFile(pyfile, 'r')
1111             except:
1112                 traceback.print_exc()
1113                 return {'CANCELLED'}
1114
1115             if self.overwrite:
1116                 for f in file_to_extract.namelist():
1117                     __class__._module_remove(path_addons, f)
1118             else:
1119                 for f in file_to_extract.namelist():
1120                     path_dest = os.path.join(path_addons, os.path.basename(f))
1121                     if os.path.exists(path_dest):
1122                         self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
1123                         return {'CANCELLED'}
1124
1125             try:  # extract the file to "addons"
1126                 file_to_extract.extractall(path_addons)
1127             except:
1128                 traceback.print_exc()
1129                 return {'CANCELLED'}
1130
1131         else:
1132             path_dest = os.path.join(path_addons, os.path.basename(pyfile))
1133
1134             if self.overwrite:
1135                 __class__._module_remove(path_addons, os.path.basename(pyfile))
1136             elif os.path.exists(path_dest):
1137                 self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
1138                 return {'CANCELLED'}
1139
1140             #if not compressed file just copy into the addon path
1141             try:
1142                 shutil.copyfile(pyfile, path_dest)
1143
1144             except:
1145                 traceback.print_exc()
1146                 return {'CANCELLED'}
1147
1148         # disable any addons we may have enabled previously and removed.
1149         # this is unlikely but do just incase. bug [#23978]
1150         addons_new = set(os.listdir(path_addons)) - contents
1151         for new_addon in addons_new:
1152             addon_utils.disable(os.path.splitext(new_addon)[0])
1153
1154         # possible the zip contains multiple addons, we could disallow this
1155         # but for now just use the first
1156         for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules):
1157             if mod.__name__ in addons_new:
1158                 info = addon_utils.module_bl_info(mod)
1159
1160                 # show the newly installed addon.
1161                 context.window_manager.addon_filter = 'All'
1162                 context.window_manager.addon_search = info["name"]
1163                 break
1164
1165         # TODO, should not be a warning.
1166         # self.report({'WARNING'}, "File installed to '%s'\n" % path_dest)
1167         return {'FINISHED'}
1168
1169     def invoke(self, context, event):
1170         wm = context.window_manager
1171         wm.fileselect_add(self)
1172         return {'RUNNING_MODAL'}
1173
1174
1175 class WM_OT_addon_expand(bpy.types.Operator):
1176     "Display more information on this add-on"
1177     bl_idname = "wm.addon_expand"
1178     bl_label = ""
1179
1180     module = StringProperty(name="Module", description="Module name of the addon to expand")
1181
1182     def execute(self, context):
1183         module_name = self.module
1184
1185         # unlikely to fail, module should have already been imported
1186         try:
1187             # mod = __import__(module_name)
1188             mod = USERPREF_PT_addons.module_get(module_name)
1189         except:
1190             import traceback
1191             traceback.print_exc()
1192             return {'CANCELLED'}
1193
1194         info = addon_utils.module_bl_info(mod)
1195         info["show_expanded"] = not info["show_expanded"]
1196         return {'FINISHED'}
1197
1198
1199 def register():
1200     bpy.utils.register_module(__name__)
1201
1202
1203 def unregister():
1204     bpy.utils.unregister_module(__name__)
1205
1206 if __name__ == "__main__":
1207     register()