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: 69f4b0bbf2a21669c05f8539193e18d8346eeae4 (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
# ##### 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 ToolSelectPanelHelper._tool_is_group(item):
                for sub_item in item:
                    yield sub_item
            else:
                yield item

    @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.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 = {}

        # ignore in background mode
        if kc is None:
            return

        for t in ToolSelectPanelHelper._tools_flatten(cls.tools_all()):
            text, mp_idname, actions = t
            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
        km_idname_active = workspace.tool_keymap or None
        mp_idname_active = workspace.tool_manipulator_group or None
        index_active = workspace.tool_index
        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):
                        index = 0
                        is_active = False
                        for i, sub_item in enumerate(item):
                            text, mp_idname, actions = sub_item
                            km, km_idname = (None, None) if actions is None else self._tool_keymap[text]
                            is_active = (
                                km_idname_active == km_idname and
                                mp_idname_active == mp_idname
                            )
                            if is_active:
                                index = i
                                break
                        del i, sub_item
                        item = item[index]
                        use_menu = True
                    else:
                        index = -1
                        use_menu = False

                    text, mp_idname, actions = item
                    km, km_idname = (None, None) if actions is None else self._tool_keymap[text]
                    is_active = (
                        km_idname_active == km_idname and
                        mp_idname_active == mp_idname
                    )

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

                    props.keymap = km_idname or ""
                    props.manipulator_group = mp_idname 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:
            props = context.button_operator
            km_idname_button = props.keymap or None
            mp_idname_button = props.manipulator_group or None
            index_button = props.index

            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]
                            text, mp_idname, actions = item
                            km, km_idname = (None, None) if actions is None else cls._tool_keymap[text]
                            is_active = (
                                km_idname_button == km_idname and
                                mp_idname_button == mp_idname
                            )
                            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(f"Unable to find toolbar group")
            return

        index = 0
        for item in item_group:
            if item is None:
                layout.separator()
                continue
            text, mp_idname, actions = item
            km, km_idname = (None, None) if actions is None else cls._tool_keymap[text]

            props = layout.operator(
                "wm.tool_set",
                text=text,
            )
            props.keymap = km_idname or ""
            props.manipulator_group = mp_idname 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)