Welcome to mirror list, hosted at ThFree Co, Russian Federation.

space_toolsystem_common.py « bl_ui « startup « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: aff9a8d6170037b2afe71d8ad17bb7b3ed419311 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>
import bpy
from bpy.types import (
    Menu,
)

__all__ = (
    "ToolSelectPanelHelper",
)


class ToolSelectPanelHelper:
    """
    Generic Class, can be used for any toolbar.

    - keymap_prefix:
      The text prefix for each key-map for this spaces tools.
    - tools_all():
      Returns all tools defined.
    - tools_from_context(context):
      Returns tools available in this context.

    Each tool is a triplet:
      ``(tool_name, manipulator_group_idname, keymap_actions)``
    For a separator in the toolbar, use ``None``.

      Where:
      ``tool_name``
        is the name to display in the interface.
      ``manipulator_group_idname``
        is an optional manipulator group to activate when the tool is set.
      ``keymap_actions``
        an optional triple of: ``(operator_id, operator_properties, keymap_item_args)``
    """

    @staticmethod
    def _tool_is_group(tool):
        return type(tool[0]) is not str

    @staticmethod
    def _tools_flatten(tools):
        for item in tools:
            if item is not None:
                if ToolSelectPanelHelper._tool_is_group(item):
                    for sub_item in item:
                        if sub_item is not None:
                            yield sub_item
                else:
                    yield item

    @classmethod
    def _tool_vars_from_def(cls, item):
        text, mp_idname, actions = item
        km, km_idname = (None, None) if actions is None else cls._tool_keymap[text]
        return (km_idname, mp_idname)

    @staticmethod
    def _tool_vars_from_active_with_index(context):
        workspace = context.workspace
        return (
            (workspace.tool_keymap or None, workspace.tool_manipulator_group or None),
            workspace.tool_index,
        )

    @staticmethod
    def _tool_vars_from_button_with_index(context):
        props = context.button_operator
        return (
            (props.keymap or None or None, props.manipulator_group or None),
            props.index,
        )

    @classmethod
    def _km_actionmouse_simple(cls, kc, text, actions):

        # standalone
        def props_assign_recursive(rna_props, py_props):
            for prop_id, value in py_props.items():
                if isinstance(value, dict):
                    props_assign_recursive(getattr(rna_props, prop_id), value)
                else:
                    setattr(rna_props, prop_id, value)

        km_idname = cls.keymap_prefix + text
        km = kc.keymaps.get(km_idname)
        if km is not None:
            return km, km_idname
        km = kc.keymaps.new(km_idname, space_type=cls.bl_space_type, region_type='WINDOW')
        for op_idname, op_props_dict, kmi_kwargs in actions:
            kmi = km.keymap_items.new(op_idname, **kmi_kwargs)
            kmi_props = kmi.properties
            if op_props_dict:
                props_assign_recursive(kmi.properties, op_props_dict)
        return km, km_idname

    @classmethod
    def register(cls):
        wm = bpy.context.window_manager

        # XXX, should we be manipulating the user-keyconfig on load?
        # Perhaps this should only add when keymap items don't already exist.
        #
        # This needs some careful consideration.
        kc = wm.keyconfigs.user

        # {tool_name: (keymap, keymap_idname, manipulator_group_idname), ...}
        cls._tool_keymap = {}

        # Track which tool-group was last used for non-active groups.
        # Blender stores the active tool-group index.
        #
        # {tool_name_first: index_in_group, ...}
        cls._tool_group_active = {}

        # ignore in background mode
        if kc is None:
            return

        for item in ToolSelectPanelHelper._tools_flatten(cls.tools_all()):
            text, mp_idname, actions = item
            if actions is not None:
                km, km_idname = cls._km_actionmouse_simple(kc, text, actions)
                cls._tool_keymap[text] = km, km_idname

    def draw(self, context):
        # XXX, this UI isn't very nice.
        # We might need to create new button types for this.
        # Since we probably want:
        # - tool-tips that include multiple key shortcuts.
        # - ability to click and hold to expose sub-tools.

        workspace = context.workspace
        tool_def_active, index_active = self._tool_vars_from_active_with_index(context)
        layout = self.layout

        for tool_items in self.tools_from_context(context):
            if tool_items:
                col = layout.column(align=True)
                for item in tool_items:
                    if item is None:
                        col = layout.column(align=True)
                        continue

                    if self._tool_is_group(item):
                        is_active = False
                        i = 0
                        for i, sub_item in enumerate(item):
                            if sub_item is None:
                                continue
                            tool_def = self._tool_vars_from_def(sub_item)
                            is_active = (tool_def == tool_def_active)
                            if is_active:
                                index = i
                                break
                        del i, sub_item

                        if is_active:
                            # not ideal, write this every time :S
                            self._tool_group_active[item[0][0]] = index
                        else:
                            index = self._tool_group_active.get(item[0][0], 0)

                        item = item[index]
                        use_menu = True
                    else:
                        index = -1
                        use_menu = False

                    tool_def = self._tool_vars_from_def(item)
                    is_active = (tool_def == tool_def_active)

                    if use_menu:
                        props = col.operator_menu_hold(
                            "wm.tool_set",
                            text=item[0],
                            depress=is_active,
                            menu="WM_MT_toolsystem_submenu",
                        )
                    else:
                        props = col.operator(
                            "wm.tool_set",
                            text=item[0],
                            depress=is_active,
                        )

                    props.keymap = tool_def[0] or ""
                    props.manipulator_group = tool_def[1] or ""
                    props.index = index

    def tools_from_context(cls, context):
        return (cls._tools[None], cls._tools.get(context.mode, ()))


# The purpose of this menu is to be a generic popup to select between tools
# in cases when a single tool allows to select alternative tools.
class WM_MT_toolsystem_submenu(Menu):
    bl_label = ""

    @staticmethod
    def _tool_group_from_button(context):
        # Lookup the tool definitions based on the space-type.
        space_type = context.space_data.type
        cls = next(
            (cls for cls in ToolSelectPanelHelper.__subclasses__()
             if cls.bl_space_type == space_type),
            None
        )
        if cls is not None:
            tool_def_button, index_button = cls._tool_vars_from_button_with_index(context)

            for item_items in cls.tools_from_context(context):
                for item_group in item_items:
                    if (item_group is not None) and ToolSelectPanelHelper._tool_is_group(item_group):
                        if index_button < len(item_group):
                            item = item_group[index_button]
                            tool_def = cls._tool_vars_from_def(item)
                            is_active = (tool_def == tool_def_button)
                            if is_active:
                                return cls, item_group, index_button
        return None, None, -1

    def draw(self, context):
        layout = self.layout
        cls, item_group, index_active = self._tool_group_from_button(context)
        if item_group is None:
            # Should never happen, just in case
            layout.label("Unable to find toolbar group")
            return

        index = 0
        for item in item_group:
            if item is None:
                layout.separator()
                continue
            tool_def = cls._tool_vars_from_def(item)
            props = layout.operator(
                "wm.tool_set",
                text=item[0],
            )
            props.keymap = tool_def[0] or ""
            props.manipulator_group = tool_def[1] or ""
            props.index = index
            index += 1


classes = (
    WM_MT_toolsystem_submenu,
)

if __name__ == "__main__":  # only for live edit.
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)