text: # ##### 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,
      Operator,
  )
  from bpy.props import (
      BoolProperty,
      CollectionProperty,
      EnumProperty,
      FloatProperty,
      IntProperty,
      StringProperty,
  )
  from bpy.app.translations import pgettext_iface as iface_
  
  rna_path_prop = StringProperty(
      name="Context Attributes",
      description="RNA context string",
      maxlen=1024,
  )
  
  rna_reverse_prop = BoolProperty(
      name="Reverse",
      description="Cycle backwards",
      default=False,
      options={'SKIP_SAVE'},
  )
  
  rna_wrap_prop = BoolProperty(
      name="Wrap",
      description="Wrap back to the first/last values",
      default=False,
      options={'SKIP_SAVE'},
  )
  
  rna_relative_prop = BoolProperty(
      name="Relative",
      description="Apply relative to the current value (delta)",
      default=False,
      options={'SKIP_SAVE'},
  )
  
  rna_space_type_prop = EnumProperty(
      name="Type",
      items=tuple(
          (e.identifier, e.name, "", e. value)
          for e in bpy.types.Space.bl_rna.properties["type"].enum_items
      ),
      default='EMPTY',
  )
  
  # Note, this can be used for more operators,
  # currently not used for all "WM_OT_context_" operators.
  rna_module_prop = StringProperty(
      name="Module",
      description="Optionally override the context with a module",
      maxlen=1024,
  )
  
  
  def context_path_validate(context, data_path):
      try:
          value = eval("context.%s" % data_path) if data_path else Ellipsis
      except AttributeError as ex:
          if str(ex).startswith("'NoneType'"):
              # One of the items in the rna path is None, just ignore this
              value = Ellipsis
          else:
              # Print invalid path, but don't show error to the users and fully
              # break the UI if the operator is bound to an event like left click.
              print("context_path_validate error: context.%s not found (invalid keymap entry?)" % data_path)
              value = Ellipsis
  
      return value
  
  
  def operator_value_is_undo(value):
      if value in {None, Ellipsis}:
          return False
  
      # typical properties or objects
      id_data = getattr(value, "id_data", Ellipsis)
  
      if id_data is None:
          return False
      elif id_data is Ellipsis:
          # handle mathutils types
          id_data = getattr(getattr(value, "owner", None), "id_data", None)
  
          if id_data is None:
              return False
  
      # return True if its a non window ID type
      return (isinstance(id_data, bpy.types.ID) and
              (not isinstance(id_data, (bpy.types.WindowManager,
                                        bpy.types.Screen,
                                        bpy.types.Brush,
                                        ))))
  
  
  def operator_path_is_undo(context, data_path):
      # note that if we have data paths that use strings this could fail
      # luckily we don't do this!
      #
      # When we can't find the data owner assume no undo is needed.
      data_path_head = data_path.rpartition(".")[0]
  
      if not data_path_head:
          return False
  
      value = context_path_validate(context, data_path_head)
  
      return operator_value_is_undo(value)
  
  
  def operator_path_undo_return(context, data_path):
      return {'FINISHED'} if operator_path_is_undo(context, data_path) else {'CANCELLED'}
  
  
  def operator_value_undo_return(value):
      return {'FINISHED'} if operator_value_is_undo(value) else {'CANCELLED'}
  
  
  def execute_context_assign(self, context):
      data_path = self.data_path
      if context_path_validate(context, data_path) is Ellipsis:
          return {'PASS_THROUGH'}
  
      if getattr(self, "relative", False):
          exec("context.%s += self.value" % data_path)
      else:
          exec("context.%s = self.value" % data_path)
  
      return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_set_boolean(Operator):
      """Set a context value"""
      bl_idname = "wm.context_set_boolean"
      bl_label = "Context Set Boolean"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: BoolProperty(
          name="Value",
          description="Assignment value",
          default=True,
      )
  
      execute = execute_context_assign
  
  
  class WM_OT_context_set_int(Operator):  # same as enum
      """Set a context value"""
      bl_idname = "wm.context_set_int"
      bl_label = "Context Set"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: IntProperty(
          name="Value",
          description="Assign value",
          default=0,
      )
      relative: rna_relative_prop
  
      execute = execute_context_assign
  
  
  class WM_OT_context_scale_float(Operator):
      """Scale a float context value"""
      bl_idname = "wm.context_scale_float"
      bl_label = "Context Scale Float"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: FloatProperty(
          name="Value",
          description="Assign value",
          default=1.0,
      )
  
      def execute(self, context):
          data_path = self.data_path
          if context_path_validate(context, data_path) is Ellipsis:
              return {'PASS_THROUGH'}
  
          value = self.value
  
          if value == 1.0:  # nothing to do
              return {'CANCELLED'}
  
          exec("context.%s *= value" % data_path)
  
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_scale_int(Operator):
      """Scale an int context value"""
      bl_idname = "wm.context_scale_int"
      bl_label = "Context Scale Int"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: FloatProperty(
          name="Value",
          description="Assign value",
          default=1.0,
      )
      always_step: BoolProperty(
          name="Always Step",
          description="Always adjust the value by a minimum of 1 when 'value' is not 1.0",
          default=True,
          options={'SKIP_SAVE'},
      )
  
      def execute(self, context):
          data_path = self.data_path
          if context_path_validate(context, data_path) is Ellipsis:
              return {'PASS_THROUGH'}
  
          value = self.value
  
          if value == 1.0:  # nothing to do
              return {'CANCELLED'}
  
          if getattr(self, "always_step", False):
              if value > 1.0:
                  add = "1"
                  func = "max"
              else:
                  add = "-1"
                  func = "min"
              exec("context.%s = %s(round(context.%s * value), context.%s + %s)" %
                   (data_path, func, data_path, data_path, add))
          else:
              exec("context.%s *= value" % data_path)
  
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_set_float(Operator):  # same as enum
      """Set a context value"""
      bl_idname = "wm.context_set_float"
      bl_label = "Context Set Float"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: FloatProperty(
          name="Value",
          description="Assignment value",
          default=0.0,
      )
      relative: rna_relative_prop
  
      execute = execute_context_assign
  
  
  class WM_OT_context_set_string(Operator):  # same as enum
      """Set a context value"""
      bl_idname = "wm.context_set_string"
      bl_label = "Context Set String"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: StringProperty(
          name="Value",
          description="Assign value",
          maxlen=1024,
      )
  
      execute = execute_context_assign
  
  
  class WM_OT_context_set_enum(Operator):
      """Set a context value"""
      bl_idname = "wm.context_set_enum"
      bl_label = "Context Set Enum"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: StringProperty(
          name="Value",
          description="Assignment value (as a string)",
          maxlen=1024,
      )
  
      execute = execute_context_assign
  
  
  class WM_OT_context_set_value(Operator):
      """Set a context value"""
      bl_idname = "wm.context_set_value"
      bl_label = "Context Set Value"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: StringProperty(
          name="Value",
          description="Assignment value (as a string)",
          maxlen=1024,
      )
  
      def execute(self, context):
          data_path = self.data_path
          if context_path_validate(context, data_path) is Ellipsis:
              return {'PASS_THROUGH'}
          exec("context.%s = %s" % (data_path, self.value))
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_toggle(Operator):
      """Toggle a context value"""
      bl_idname = "wm.context_toggle"
      bl_label = "Context Toggle"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      module: rna_module_prop
  
      def execute(self, context):
          data_path = self.data_path
  
          module = self.module
          if not module:
              base = context
          else:
              from importlib import import_module
              base = import_module(self.module)
  
          if context_path_validate(base, data_path) is Ellipsis:
              return {'PASS_THROUGH'}
  
          exec("base.%s = not (base.%s)" % (data_path, data_path))
  
          return operator_path_undo_return(base, data_path)
  
  
  class WM_OT_context_toggle_enum(Operator):
      """Toggle a context value"""
      bl_idname = "wm.context_toggle_enum"
      bl_label = "Context Toggle Values"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value_1: StringProperty(
          name="Value",
          description="Toggle enum",
          maxlen=1024,
      )
      value_2: StringProperty(
          name="Value",
          description="Toggle enum",
          maxlen=1024,
      )
  
      def execute(self, context):
          data_path = self.data_path
  
          if context_path_validate(context, data_path) is Ellipsis:
              return {'PASS_THROUGH'}
  
          # failing silently is not ideal, but we don't want errors for shortcut
          # keys that some values that are only available in a particular context
          try:
              exec("context.%s = ('%s', '%s')[context.%s != '%s']" %
                   (data_path, self.value_1,
                    self.value_2, data_path,
                    self.value_2,
                    ))
          except:
              return {'PASS_THROUGH'}
  
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_cycle_int(Operator):
      """Set a context value (useful for cycling active material, """ \
          """vertex keys, groups, etc.)"""
      bl_idname = "wm.context_cycle_int"
      bl_label = "Context Int Cycle"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      reverse: rna_reverse_prop
      wrap: rna_wrap_prop
  
      def execute(self, context):
          data_path = self.data_path
          value = context_path_validate(context, data_path)
          if value is Ellipsis:
              return {'PASS_THROUGH'}
  
          if self.reverse:
              value -= 1
          else:
              value += 1
  
          exec("context.%s = value" % data_path)
  
          if self.wrap:
              if value != eval("context.%s" % data_path):
                  # relies on rna clamping integers out of the range
                  if self.reverse:
                      value = (1 << 31) - 1
                  else:
                      value = -1 << 31
  
                  exec("context.%s = value" % data_path)
  
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_cycle_enum(Operator):
      """Toggle a context value"""
      bl_idname = "wm.context_cycle_enum"
      bl_label = "Context Enum Cycle"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      reverse: rna_reverse_prop
      wrap: rna_wrap_prop
  
      def execute(self, context):
          data_path = self.data_path
          value = context_path_validate(context, data_path)
          if value is Ellipsis:
              return {'PASS_THROUGH'}
  
          orig_value = value
  
          # Have to get rna enum values
          rna_struct_str, rna_prop_str = data_path.rsplit('.', 1)
          i = rna_prop_str.find('[')
  
          # just in case we get "context.foo.bar[0]"
          if i != -1:
              rna_prop_str = rna_prop_str[0:i]
  
          rna_struct = eval("context.%s.rna_type" % rna_struct_str)
  
          rna_prop = rna_struct.properties[rna_prop_str]
  
          if type(rna_prop) != bpy.types.EnumProperty:
              raise Exception("expected an enum property")
  
          enums = rna_struct.properties[rna_prop_str].enum_items.keys()
          orig_index = enums.index(orig_value)
  
          # Have the info we need, advance to the next item.
          #
          # When wrap's disabled we may set the value to its self,
          # this is done to ensure update callbacks run.
          if self.reverse:
              if orig_index == 0:
                  advance_enum = enums[-1] if self.wrap else enums[0]
              else:
                  advance_enum = enums[orig_index - 1]
          else:
              if orig_index == len(enums) - 1:
                  advance_enum = enums[0] if self.wrap else enums[-1]
              else:
                  advance_enum = enums[orig_index + 1]
  
          # set the new value
          exec("context.%s = advance_enum" % data_path)
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_cycle_array(Operator):
      """Set a context array value """ \
          """(useful for cycling the active mesh edit mode)"""
      bl_idname = "wm.context_cycle_array"
      bl_label = "Context Array Cycle"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      reverse: rna_reverse_prop
  
      def execute(self, context):
          data_path = self.data_path
          value = context_path_validate(context, data_path)
          if value is Ellipsis:
              return {'PASS_THROUGH'}
  
          def cycle(array):
              if self.reverse:
                  array.insert(0, array.pop())
              else:
                  array.append(array.pop(0))
              return array
  
          exec("context.%s = cycle(context.%s[:])" % (data_path, data_path))
  
          return operator_path_undo_return(context, data_path)
  
  
  class WM_OT_context_menu_enum(Operator):
      bl_idname = "wm.context_menu_enum"
      bl_label = "Context Enum Menu"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
  
      def execute(self, context):
          data_path = self.data_path
          value = context_path_validate(context, data_path)
  
          if value is Ellipsis:
              return {'PASS_THROUGH'}
  
          base_path, prop_string = data_path.rsplit(".", 1)
          value_base = context_path_validate(context, base_path)
          prop = value_base.bl_rna.properties[prop_string]
  
          def draw_cb(self, context):
              layout = self.layout
              layout.prop(value_base, prop_string, expand=True)
  
          context.window_manager.popup_menu(draw_func=draw_cb, title=prop.name, icon=prop.icon)
  
          return {'FINISHED'}
  
  
  class WM_OT_context_pie_enum(Operator):
      bl_idname = "wm.context_pie_enum"
      bl_label = "Context Enum Pie"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
  
      def invoke(self, context, event):
          wm = context.window_manager
          data_path = self.data_path
          value = context_path_validate(context, data_path)
  
          if value is Ellipsis:
              return {'PASS_THROUGH'}
  
          base_path, prop_string = data_path.rsplit(".", 1)
          value_base = context_path_validate(context, base_path)
          prop = value_base.bl_rna.properties[prop_string]
  
          def draw_cb(self, context):
              layout = self.layout
              layout.prop(value_base, prop_string, expand=True)
  
          wm.popup_menu_pie(draw_func=draw_cb, title=prop.name, icon=prop.icon, event=event)
  
          return {'FINISHED'}
  
  
  class WM_OT_operator_pie_enum(Operator):
      bl_idname = "wm.operator_pie_enum"
      bl_label = "Operator Enum Pie"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: StringProperty(
          name="Operator",
          description="Operator name (in python as string)",
          maxlen=1024,
      )
      prop_string: StringProperty(
          name="Property",
          description="Property name (as a string)",
          maxlen=1024,
      )
  
      def invoke(self, context, event):
          wm = context.window_manager
  
          data_path = self.data_path
          prop_string = self.prop_string
  
          # same as eval("bpy.ops." + data_path)
          op_mod_str, ob_id_str = data_path.split(".", 1)
          op = getattr(getattr(bpy.ops, op_mod_str), ob_id_str)
          del op_mod_str, ob_id_str
  
          try:
              op_rna = op.get_rna_type()
          except KeyError:
              self.report({'ERROR'}, "Operator not found: bpy.ops.%s" % data_path)
              return {'CANCELLED'}
  
          def draw_cb(self, context):
              layout = self.layout
              pie = layout.menu_pie()
              pie.operator_enum(data_path, prop_string)
  
          wm.popup_menu_pie(draw_func=draw_cb, title=op_rna.name, event=event)
  
          return {'FINISHED'}
  
  
  class WM_OT_context_set_id(Operator):
      """Set a context value to an ID data-block"""
      bl_idname = "wm.context_set_id"
      bl_label = "Set Library ID"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path_prop
      value: StringProperty(
          name="Value",
          description="Assign value",
          maxlen=1024,
      )
  
      def execute(self, context):
          value = self.value
          data_path = self.data_path
  
          # match the pointer type from the target property to bpy.data.*
          # so we lookup the correct list.
          data_path_base, data_path_prop = data_path.rsplit(".", 1)
          data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
          data_prop_rna_type = data_prop_rna.fixed_type
  
          id_iter = None
  
          for prop in bpy.data.rna_type.properties:
              if prop.rna_type.identifier == "CollectionProperty":
                  if prop.fixed_type == data_prop_rna_type:
                      id_iter = prop.identifier
                      break
  
          if id_iter:
              value_id = getattr(bpy.data, id_iter).get(value)
              exec("context.%s = value_id" % data_path)
  
          return operator_path_undo_return(context, data_path)
  
  
  doc_id = StringProperty(
      name="Doc ID",
      maxlen=1024,
      options={'HIDDEN'},
  )
  
  data_path_iter = StringProperty(
      description="The data path relative to the context, must point to an iterable")
  
  data_path_item = StringProperty(
      description="The data path from each iterable to the value (int or float)")
  
  
  class WM_OT_context_collection_boolean_set(Operator):
      """Set boolean values for a collection of items"""
      bl_idname = "wm.context_collection_boolean_set"
      bl_label = "Context Collection Boolean Set"
      bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
  
      data_path_iter: data_path_iter
      data_path_item: data_path_item
  
      type: EnumProperty(
          name="Type",
          items=(
              ('TOGGLE', "Toggle", ""),
              ('ENABLE', "Enable", ""),
              ('DISABLE', "Disable", ""),
          ),
      )
  
      def execute(self, context):
          data_path_iter = self.data_path_iter
          data_path_item = self.data_path_item
  
          items = list(getattr(context, data_path_iter))
          items_ok = []
          is_set = False
          for item in items:
              try:
                  value_orig = eval("item." + data_path_item)
              except:
                  continue
  
              if value_orig is True:
                  is_set = True
              elif value_orig is False:
                  pass
              else:
                  self.report({'WARNING'}, "Non boolean value found: %s[ ].%s" %
                              (data_path_iter, data_path_item))
                  return {'CANCELLED'}
  
              items_ok.append(item)
  
          # avoid undo push when nothing to do
          if not items_ok:
              return {'CANCELLED'}
  
          if self.type == 'ENABLE':
              is_set = True
          elif self.type == 'DISABLE':
              is_set = False
          else:
              is_set = not is_set
  
          exec_str = "item.%s = %s" % (data_path_item, is_set)
          for item in items_ok:
              exec(exec_str)
  
          return operator_value_undo_return(item)
  
  
  class WM_OT_context_modal_mouse(Operator):
      """Adjust arbitrary values with mouse input"""
      bl_idname = "wm.context_modal_mouse"
      bl_label = "Context Modal Mouse"
      bl_options = {'GRAB_CURSOR', 'BLOCKING', 'UNDO', 'INTERNAL'}
  
      data_path_iter: data_path_iter
      data_path_item: data_path_item
      header_text: StringProperty(
          name="Header Text",
          description="Text to display in header during scale",
      )
  
      input_scale: FloatProperty(
          description="Scale the mouse movement by this value before applying the delta",
          default=0.01,
          options={'SKIP_SAVE'},
      )
      invert: BoolProperty(
          description="Invert the mouse input",
          default=False,
          options={'SKIP_SAVE'},
      )
      initial_x: IntProperty(options={'HIDDEN'})
  
      def _values_store(self, context):
          data_path_iter = self.data_path_iter
          data_path_item = self.data_path_item
  
          self._values = values = {}
  
          for item in getattr(context, data_path_iter):
              try:
                  value_orig = eval("item." + data_path_item)
              except:
                  continue
  
              # check this can be set, maybe this is library data.
              try:
                  exec("item.%s = %s" % (data_path_item, value_orig))
              except:
                  continue
  
              values[item] = value_orig
  
      def _values_delta(self, delta):
          delta *= self.input_scale
          if self.invert:
              delta = - delta
  
          data_path_item = self.data_path_item
          for item, value_orig in self._values.items():
              if type(value_orig) == int:
                  exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
              else:
                  exec("item.%s = %f" % (data_path_item, value_orig + delta))
  
      def _values_restore(self):
          data_path_item = self.data_path_item
          for item, value_orig in self._values.items():
              exec("item.%s = %s" % (data_path_item, value_orig))
  
          self._values.clear()
  
      def _values_clear(self):
          self._values.clear()
  
      def modal(self, context, event):
          event_type = event.type
  
          if event_type == 'MOUSEMOVE':
              delta = event.mouse_x - self.initial_x
              self._values_delta(delta)
              header_text = self.header_text
              if header_text:
                  if len(self._values) == 1:
                      (item, ) = self._values.keys()
                      header_text = header_text % eval("item.%s" % self.data_path_item)
                  else:
                      header_text = (self.header_text % delta) + " (delta)"
                  context.area.header_text_set(header_text)
  
          elif 'LEFTMOUSE' == event_type:
              item = next(iter(self._values.keys()))
              self._values_clear()
              context.area.header_text_set(None)
              return operator_value_undo_return(item)
  
          elif event_type in {'RIGHTMOUSE', 'ESC'}:
              self._values_restore()
              context.area.header_text_set(None)
              return {'CANCELLED'}
  
          return {'RUNNING_MODAL'}
  
      def invoke(self, context, event):
          self._values_store(context)
  
          if not self._values:
              self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
                          (self.data_path_iter, self.data_path_item))
  
              return {'CANCELLED'}
          else:
              self.initial_x = event.mouse_x
  
              context.window_manager.modal_handler_add(self)
              return {'RUNNING_MODAL'}
  
  
  class WM_OT_url_open(Operator):
      """Open a website in the web browser"""
      bl_idname = "wm.url_open"
      bl_label = ""
      bl_options = {'INTERNAL'}
  
      url: StringProperty(
          name="URL",
          description="URL to open",
      )
  
      def execute(self, _context):
          import webbrowser
          webbrowser.open(self.url)
          return {'FINISHED'}
  
  
  class WM_OT_url_open_preset(Operator):
      """Open a preset website in the web browser"""
      bl_idname = "wm.url_open_preset"
      bl_label = "Open Preset Website"
      bl_options = {'INTERNAL'}
  
      type: EnumProperty(
          name="Site",
          items=lambda self, _context: (
              item for (item, _) in WM_OT_url_open_preset.preset_items
          ),
      )
  
      id: StringProperty(
          name="Identifier",
          description="Optional identifier",
      )
  
      def _url_from_bug(self, _context):
          from bl_ui_utils.bug_report_url import url_prefill_from_blender
          return url_prefill_from_blender()
  
      def _url_from_bug_addon(self, _context):
          from bl_ui_utils.bug_report_url import url_prefill_from_blender
          return url_prefill_from_blender(addon_info=self.id)
  
      def _url_from_release_notes(self, _context):
          return "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
  
      def _url_from_manual(self, _context):
          if bpy.app.version_cycle in {"rc", "release"}:
              manual_version = "%d.%d" % bpy.app.version[:2]
          else:
              manual_version = "dev"
          return "https://docs.blender.org/manual/en/" + manual_version + "/"
  
      # This list is: (enum_item, url) pairs.
      # Allow dynamically extending.
      preset_items = [
          # Dynamic URL's.
          (('BUG', "Bug",
            "Report a bug with pre-filled version information"),
           _url_from_bug),
          (('BUG_ADDON', "Add-on Bug",
            "Report a bug in an add-on"),
           _url_from_bug_addon),
          (('RELEASE_NOTES', "Release Notes",
            "Read about whats new in this version of Blender"),
           _url_from_release_notes),
          (('MANUAL', "Manual",
            "The reference manual for this version of Blender"),
           _url_from_manual),
  
          # Static URL's.
          (('FUND', "Development Fund",
            "The donation program to support maintenance and improvements"),
           "https://fund.blender.org"),
          (('BLENDER', "blender.org",
            "Blender's official web-site"),
           "https://www.blender.org"),
          (('CREDITS', "Credits",
            "Lists committers to Blender's source code"),
           "https://www.blender.org/about/credits/"),
      ]
  
      def execute(self, context):
          url = None
          type = self.type
          for (item_id, _, _), url in self.preset_items:
              if item_id == type:
                  if callable(url):
                      url = url(self, context)
                  break
  
          import webbrowser
          webbrowser.open(url)
  
          return {'FINISHED'}
  
  
  class WM_OT_path_open(Operator):
      """Open a path in a file browser"""
      bl_idname = "wm.path_open"
      bl_label = ""
      bl_options = {'INTERNAL'}
  
      filepath: StringProperty(
          subtype='FILE_PATH',
          options={'SKIP_SAVE'},
      )
  
      def execute(self, _context):
          import sys
          import os
          import subprocess
  
          filepath = self.filepath
  
          if not filepath:
              self.report({'ERROR'}, "File path was not set")
              return {'CANCELLED'}
  
          filepath = bpy.path.abspath(filepath)
          filepath = os.path.normpath(filepath)
  
          if not os.path.exists(filepath):
              self.report({'ERROR'}, "File '%s' not found" % filepath)
              return {'CANCELLED'}
  
          if sys.platform[:3] == "win":
              os.startfile(filepath)
          elif sys.platform == "darwin":
              subprocess.check_call(["open", filepath])
          else:
              try:
                  subprocess.check_call(["xdg-open", filepath])
              except:
                  # xdg-open *should* be supported by recent Gnome, KDE, Xfce
                  import traceback
                  traceback.print_exc()
  
          return {'FINISHED'}
  
  
  def _wm_doc_get_id(doc_id, do_url=True, url_prefix="", report=None):
  
      def operator_exists_pair(a, b):
          # Not fast, this is only for docs.
          return b in dir(getattr(bpy.ops, a))
  
      def operator_exists_single(a):
          a, b = a.partition("_OT_")[::2]
          return operator_exists_pair(a.lower(), b)
  
      id_split = doc_id.split(".")
      url = rna = None
  
      if len(id_split) == 1:  # rna, class
          if do_url:
              url = "%s/bpy.types.%s.html" % (url_prefix, id_split[0])
          else:
              rna = "bpy.types.%s" % id_split[0]
  
      elif len(id_split) == 2:  # rna, class.prop
          class_name, class_prop = id_split
  
          # an operator (common case - just button referencing an op)
          if operator_exists_pair(class_name, class_prop):
              if do_url:
                  url = (
                      "%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
                      (url_prefix, class_name, class_name, class_prop)
                  )
              else:
                  rna = "bpy.ops.%s.%s" % (class_name, class_prop)
          elif operator_exists_single(class_name):
              # note: ignore the prop name since we don't have a way to link into it
              class_name, class_prop = class_name.split("_OT_", 1)
              class_name = class_name.lower()
              if do_url:
                  url = (
                      "%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
                      (url_prefix, class_name, class_name, class_prop)
                  )
              else:
                  rna = "bpy.ops.%s.%s" % (class_name, class_prop)
          else:
              # An RNA setting, common case.
  
              # Check the built-in RNA types.
              rna_class = getattr(bpy.types, class_name, None)
              if rna_class is None:
                  # Check class for dynamically registered types.
                  rna_class = bpy.types.PropertyGroup.bl_rna_get_subclass_py(class_name)
  
              if rna_class is None:
                  if report is not None:
                      report({'ERROR'}, iface_("Type \"%s\" can not be found") % class_name)
                  return None
  
              # Detect if this is a inherited member and use that name instead.
              rna_parent = rna_class.bl_rna
              rna_prop = rna_parent.properties.get(class_prop)
              if rna_prop:
                  rna_parent = rna_parent.base
                  while rna_parent and rna_prop == rna_parent.properties.get(class_prop):
                      class_name = rna_parent.identifier
                      rna_parent = rna_parent.base
  
                  if do_url:
                      url = (
                          "%s/bpy.types.%s.html#bpy.types.%s.%s" %
                          (url_prefix, class_name, class_name, class_prop)
                      )
                  else:
                      rna = "bpy.types.%s.%s" % (class_name, class_prop)
              else:
                  # We assume this is custom property, only try to generate generic url/rna_id...
                  if do_url:
                      url = ("%s/bpy.types.bpy_struct.html#bpy.types.bpy_struct.items" % (url_prefix,))
                  else:
                      rna = "bpy.types.bpy_struct"
  
      return url if do_url else rna
  
  
  class WM_OT_doc_view_manual(Operator):
      """Load online manual"""
      bl_idname = "wm.doc_view_manual"
      bl_label = "View Manual"
  
      doc_id: doc_id
  
      @staticmethod
      def _find_reference(rna_id, url_mapping, verbose=True):
          if verbose:
              print("online manual check for: '%s'... " % rna_id)
          from fnmatch import fnmatchcase
          # XXX, for some reason all RNA ID's are stored lowercase
          # Adding case into all ID's isn't worth the hassle so force lowercase.
          rna_id = rna_id.lower()
          for pattern, url_suffix in url_mapping:
              if fnmatchcase(rna_id, pattern):
                  if verbose:
                      print("            match found: '%s' --> '%s'" % (pattern, url_suffix))
                  return url_suffix
          if verbose:
              print("match not found")
          return None
  
      @staticmethod
      def _lookup_rna_url(rna_id, verbose=True):
          for prefix, url_manual_mapping in bpy.utils.manual_map():
              rna_ref = WM_OT_doc_view_manual._find_reference(rna_id, url_manual_mapping, verbose=verbose)
              if rna_ref is not None:
                  url = prefix + rna_ref
                  return url
  
      def execute(self, _context):
          rna_id = _wm_doc_get_id(self.doc_id, do_url=False, report=self.report)
          if rna_id is None:
              return {'CANCELLED'}
  
          url = self._lookup_rna_url(rna_id)
  
          if url is None:
              self.report(
                  {'WARNING'},
                  "No reference available %r, "
                  "Update info in 'rna_manual_reference.py' "
                  "or callback to bpy.utils.manual_map()" %
                  self.doc_id
              )
              return {'CANCELLED'}
          else:
              import webbrowser
              webbrowser.open(url)
              return {'FINISHED'}
  
  
  class WM_OT_doc_view(Operator):
      """Open online reference docs in a web browser"""
      bl_idname = "wm.doc_view"
      bl_label = "View Documentation"
  
      doc_id: doc_id
      if bpy.app.version_cycle in {"release", "rc", "beta"}:
          _prefix = ("https://docs.blender.org/api/%d.%d" %
                     (bpy.app.version[0], bpy.app.version[1]))
      else:
          _prefix = ("https://docs.blender.org/api/master")
  
      def execute(self, _context):
          url = _wm_doc_get_id(self.doc_id, do_url=True, url_prefix=self._prefix, report=self.report)
          if url is None:
              return {'CANCELLED'}
  
          import webbrowser
          webbrowser.open(url)
  
          return {'FINISHED'}
  
  
  rna_path = StringProperty(
      name="Property Edit",
      description="Property data_path edit",
      maxlen=1024,
      options={'HIDDEN'},
  )
  
  rna_value = StringProperty(
      name="Property Value",
      description="Property value edit",
      maxlen=1024,
  )
  
  rna_default = StringProperty(
      name="Default Value",
      description="Default value of the property. Important for NLA mixing",
      maxlen=1024,
  )
  
  rna_custom_property = StringProperty(
      name="Property Name",
      description="Property name edit",
      # Match `MAX_IDPROP_NAME - 1` in Blender's source.
      maxlen=63,
  )
  
  rna_min = FloatProperty(
      name="Min",
      description="Minimum value of the property",
      default=-10000.0,
      precision=3,
  )
  
  rna_max = FloatProperty(
      name="Max",
      description="Maximum value of the property",
      default=10000.0,
      precision=3,
  )
  
  rna_use_soft_limits = BoolProperty(
      name="Use Soft Limits",
      description="Limits the Property Value slider to a range, values outside the range must be inputted numerically",
  )
  
  rna_is_overridable_library = BoolProperty(
      name="Is Library Overridable",
      description="Allow the property to be overridden when the data-block is linked",
      default=False,
  )
  
  # Most useful entries of rna_enum_property_subtype_items for number arrays:
  rna_vector_subtype_items = (
      ('NONE', "Plain Data", "Data values without special behavior"),
      ('COLOR', "Linear Color", "Color in the linear space"),
      ('COLOR_GAMMA', "Gamma-Corrected Color", "Color in the gamma corrected space"),
      ('EULER', "Euler Angles", "Euler rotation angles in radians"),
      ('QUATERNION', "Quaternion Rotation", "Quaternion rotation (affects NLA blending)"),
  )
  
  
  class WM_OT_properties_edit(Operator):
      """Edit the attributes of the property"""
      bl_idname = "wm.properties_edit"
      bl_label = "Edit Property"
      # register only because invoke_props_popup requires.
      bl_options = {'REGISTER', 'INTERNAL'}
  
      data_path: rna_path
      property: rna_custom_property
      value: rna_value
      default: rna_default
      min: rna_min
      max: rna_max
      use_soft_limits: rna_use_soft_limits
      is_overridable_library: rna_is_overridable_library
      soft_min: rna_min
      soft_max: rna_max
      description: StringProperty(
          name="Tooltip",
      )
      subtype: EnumProperty(
          name="Subtype",
          items=lambda self, _context: WM_OT_properties_edit.subtype_items,
      )
  
      subtype_items = rna_vector_subtype_items
  
      def _init_subtype(self, prop_type, is_array, subtype):
          subtype = subtype or 'NONE'
          subtype_items = rna_vector_subtype_items
  
          # Add a temporary enum entry to preserve unknown subtypes
          if not any(subtype == item[0] for item in subtype_items):
              subtype_items += ((subtype, subtype, ""),)
  
          WM_OT_properties_edit.subtype_items = subtype_items
          self.subtype = subtype
  
      def _cmp_props_get(self):
          # Changing these properties will refresh the UI
          return {
              "use_soft_limits": self.use_soft_limits,
              "soft_range": (self.soft_min, self.soft_max),
              "hard_range": (self.min, self.max),
          }
  
      def get_value_eval(self):
          try:
              value_eval = eval(self.value)
              # assert else None -> None, not "None", see T33431.
              assert(type(value_eval) in {str, float, int, bool, tuple, list})
          except:
              value_eval = self.value
  
          return value_eval
  
      def get_default_eval(self):
          try:
              default_eval = eval(self.default)
              # assert else None -> None, not "None", see T33431.
              assert(type(default_eval) in {str, float, int, bool, tuple, list})
          except:
              default_eval = self.default
  
          return default_eval
  
      def execute(self, context):
          from rna_prop_ui import (
              rna_idprop_ui_prop_get,
              rna_idprop_ui_prop_clear,
              rna_idprop_ui_prop_update,
              rna_idprop_ui_prop_default_set,
              rna_idprop_value_item_type,
          )
  
          data_path = self.data_path
          prop = self.property
          prop_escape = bpy.utils.escape_identifier(prop)
  
          prop_old = getattr(self, "_last_prop", [None])[0]
  
          if prop_old is None:
              self.report({'ERROR'}, "Direct execution not supported")
              return {'CANCELLED'}
  
          value_eval = self.get_value_eval()
          default_eval = self.get_default_eval()
  
          # First remove
          item = eval("context.%s" % data_path)
  
          if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference):
              self.report({'ERROR'}, "Cannot edit properties from override data")
              return {'CANCELLED'}
  
          prop_type_old = type(item[prop_old])
  
          rna_idprop_ui_prop_clear(item, prop_old)
          del item[prop_old]
  
          # Reassign
          item[prop] = value_eval
          item.property_overridable_library_set('["%s"]' % prop_escape, self.is_overridable_library)
          rna_idprop_ui_prop_update(item, prop)
  
          self._last_prop[:] = [prop]
  
          prop_value = item[prop]
          prop_type_new = type(prop_value)
          prop_type, is_array = rna_idprop_value_item_type(prop_value)
  
          prop_ui = rna_idprop_ui_prop_get(item, prop)
  
          if prop_type in {float, int}:
              prop_ui["min"] = prop_type(self.min)
              prop_ui["max"] = prop_type(self.max)
  
              if self.use_soft_limits:
                  prop_ui["soft_min"] = prop_type(self.soft_min)
                  prop_ui["soft_max"] = prop_type(self.soft_max)
              else:
                  prop_ui["soft_min"] = prop_type(self.min)
                  prop_ui["soft_max"] = prop_type(self.max)
  
          if prop_type == float and is_array and self.subtype != 'NONE':
              prop_ui["subtype"] = self.subtype
          else:
              prop_ui.pop("subtype", None)
  
          prop_ui["description"] = self.description
  
          rna_idprop_ui_prop_default_set(item, prop, default_eval)
  
          # If we have changed the type of the property, update its potential anim curves!
          if prop_type_old != prop_type_new:
              data_path = '["%s"]' % prop_escape
              done = set()
  
              def _update(fcurves):
                  for fcu in fcurves:
                      if fcu not in done and fcu.data_path == data_path:
                          fcu.update_autoflags(item)
                          done.add(fcu)
  
              def _update_strips(strips):
                  for st in strips:
                      if st.type == 'CLIP' and st.action:
                          _update(st.action.fcurves)
                      elif st.type == 'META':
                          _update_strips(st.strips)
  
              adt = getattr(item, "animation_data", None)
              if adt is not None:
                  if adt.action:
                      _update(adt.action.fcurves)
                  if adt.drivers:
                      _update(adt.drivers)
                  if adt.nla_tracks:
                      for nt in adt.nla_tracks:
                          _update_strips(nt.strips)
  
          # Otherwise existing buttons which reference freed
          # memory may crash Blender T26510.
          # context.area.tag_redraw()
          for win in context.window_manager.windows:
              for area in win.screen.areas:
                  area.tag_redraw()
  
          return {'FINISHED'}
  
      def invoke(self, context, _event):
          from rna_prop_ui import (
              rna_idprop_ui_prop_get,
              rna_idprop_value_to_python,
              rna_idprop_value_item_type
          )
  
          prop = self.property
          prop_escape = bpy.utils.escape_identifier(prop)
  
          data_path = self.data_path
  
          if not data_path:
              self.report({'ERROR'}, "Data path not set")
              return {'CANCELLED'}
  
          self._last_prop = [prop]
  
          item = eval("context.%s" % data_path)
  
          if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference):
              self.report({'ERROR'}, "Cannot edit properties from override data")
              return {'CANCELLED'}
  
          # retrieve overridable static
          is_overridable = item.is_property_overridable_library('["%s"]' % prop_escape)
          self.is_overridable_library = bool(is_overridable)
  
          # default default value
          prop_type, is_array = rna_idprop_value_item_type(self.get_value_eval())
          if prop_type in {int, float}:
              self.default = str(prop_type(0))
          else:
              self.default = ""
  
          # setup defaults
          prop_ui = rna_idprop_ui_prop_get(item, prop, False)  # don't create
          if prop_ui:
              self.min = prop_ui.get("min", -1000000000)
              self.max = prop_ui.get("max", 1000000000)
              self.description = prop_ui.get("description", "")
  
              defval = prop_ui.get("default", None)
              if defval is not None:
                  self.default = str(rna_idprop_value_to_python(defval))
  
              self.soft_min = prop_ui.get("soft_min", self.min)
              self.soft_max = prop_ui.get("soft_max", self.max)
              self.use_soft_limits = (
                  self.min != self.soft_min or
                  self.max != self.soft_max
              )
  
              subtype = prop_ui.get("subtype", None)
          else:
              subtype = None
  
          self._init_subtype(prop_type, is_array, subtype)
  
          # store for comparison
          self._cmp_props = self._cmp_props_get()
  
          wm = context.window_manager
          return wm.invoke_props_dialog(self)
  
      def check(self, _context):
          cmp_props = self._cmp_props_get()
          changed = False
          if self._cmp_props != cmp_props:
              if cmp_props["use_soft_limits"]:
                  if cmp_props["soft_range"] != self._cmp_props["soft_range"]:
                      self.min = min(self.min, self.soft_min)
                      self.max = max(self.max, self.soft_max)
                      changed = True
                  if cmp_props["hard_range"] != self._cmp_props["hard_range"]:
                      self.soft_min = max(self.min, self.soft_min)
                      self.soft_max = min(self.max, self.soft_max)
                      changed = True
              else:
                  if cmp_props["soft_range"] != cmp_props["hard_range"]:
                      self.soft_min = self.min
                      self.soft_max = self.max
                      changed = True
  
              changed |= (cmp_props["use_soft_limits"] != self._cmp_props["use_soft_limits"])
  
              if changed:
                  cmp_props = self._cmp_props_get()
  
              self._cmp_props = cmp_props
  
          return changed
  
      def draw(self, _context):
          from rna_prop_ui import (
              rna_idprop_value_item_type,
          )
  
          layout = self.layout
  
          layout.use_property_split = True
          layout.use_property_decorate = False
  
          layout.prop(self, "property")
          layout.prop(self, "value")
  
          value = self.get_value_eval()
          proptype, is_array = rna_idprop_value_item_type(value)
  
          row = layout.row()
          row.enabled = proptype in {int, float, str}
          row.prop(self, "default")
  
          col = layout.column(align=True)
          col.prop(self, "min")
          col.prop(self, "max")
  
          col = layout.column()
          col.prop(self, "is_overridable_library")
          col.prop(self, "use_soft_limits")
  
          col = layout.column(align=True)
          col.enabled = self.use_soft_limits
          col.prop(self, "soft_min", text="Soft Min")
          col.prop(self, "soft_max", text="Max")
          layout.prop(self, "description")
  
          if is_array and proptype == float:
              layout.prop(self, "subtype")
  
  
  class WM_OT_properties_add(Operator):
      """Add your own property to the data-block"""
      bl_idname = "wm.properties_add"
      bl_label = "Add Property"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path
  
      def execute(self, context):
          from rna_prop_ui import (
              rna_idprop_ui_create,
          )
  
          data_path = self.data_path
          item = eval("context.%s" % data_path)
  
          if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference):
              self.report({'ERROR'}, "Cannot add properties to override data")
              return {'CANCELLED'}
  
          def unique_name(names):
              prop = "prop"
              prop_new = prop
              i = 1
              while prop_new in names:
                  prop_new = prop + str(i)
                  i += 1
  
              return prop_new
  
          prop = unique_name({
              *item.keys(),
              *type(item).bl_rna.properties.keys(),
          })
  
          rna_idprop_ui_create(item, prop, default=1.0)
  
          return {'FINISHED'}
  
  
  class WM_OT_properties_context_change(Operator):
      """Jump to a different tab inside the properties editor"""
      bl_idname = "wm.properties_context_change"
      bl_label = ""
      bl_options = {'INTERNAL'}
  
      context: StringProperty(
          name="Context",
          maxlen=64,
      )
  
      def execute(self, context):
          context.space_data.context = self.context
          return {'FINISHED'}
  
  
  class WM_OT_properties_remove(Operator):
      """Internal use (edit a property data_path)"""
      bl_idname = "wm.properties_remove"
      bl_label = "Remove Property"
      bl_options = {'UNDO', 'INTERNAL'}
  
      data_path: rna_path
      property: rna_custom_property
  
      def execute(self, context):
          from rna_prop_ui import (
              rna_idprop_ui_prop_clear,
              rna_idprop_ui_prop_update,
          )
          data_path = self.data_path
          item = eval("context.%s" % data_path)
  
          if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference):
              self.report({'ERROR'}, "Cannot remove properties from override data")
              return {'CANCELLED'}
  
          prop = self.property
          rna_idprop_ui_prop_update(item, prop)
          del item[prop]
          rna_idprop_ui_prop_clear(item, prop)
  
          return {'FINISHED'}
  
  
  class WM_OT_sysinfo(Operator):
      """Generate system information, saved into a text file"""
  
      bl_idname = "wm.sysinfo"
      bl_label = "Save System Info"
  
      filepath: StringProperty(
          subtype='FILE_PATH',
          options={'SKIP_SAVE'},
      )
  
      def execute(self, _context):
          import sys_info
          sys_info.write_sysinfo(self.filepath)
          return {'FINISHED'}
  
      def invoke(self, context, _event):
          import os
  
          if not self.filepath:
              self.filepath = os.path.join(
                  os.path.expanduser("~"), "system-info.txt")
  
          wm = context.window_manager
          wm.fileselect_add(self)
          return {'RUNNING_MODAL'}
  
  
  class WM_OT_operator_cheat_sheet(Operator):
      """List all the operators in a text-block, useful for scripting"""
      bl_idname = "wm.operator_cheat_sheet"
      bl_label = "Operator Cheat Sheet"
  
      def execute(self, _context):
          op_strings = []
          tot = 0
          for op_module_name in dir(bpy.ops):
              op_module = getattr(bpy.ops, op_module_name)
              for op_submodule_name in dir(op_module):
                  op = getattr(op_module, op_submodule_name)
                  text = repr(op)
                  if text.split("\n")[-1].startswith("bpy.ops."):
                      op_strings.append(text)
                      tot += 1
  
              op_strings.append('')
  
          textblock = bpy.data.texts.new("OperatorList.txt")
          textblock.write('# %d Operators\n\n' % tot)
          textblock.write('\n'.join(op_strings))
          self.report({'INFO'}, "See OperatorList.txt text block")
          return {'FINISHED'}
  
  
  # -----------------------------------------------------------------------------
  # Add-on Operators
  
  class WM_OT_owner_enable(Operator):
      """Enable workspace owner ID"""
      bl_idname = "wm.owner_enable"
      bl_label = "Enable Add-on"
  
      owner_id: StringProperty(
          name="UI Tag",
      )
  
      def execute(self, context):
          workspace = context.workspace
          workspace.owner_ids.new(self.owner_id)
          return {'FINISHED'}
  
  
  class WM_OT_owner_disable(Operator):
      """Enable workspace owner ID"""
      bl_idname = "wm.owner_disable"
      bl_label = "Disable UI Tag"
  
      owner_id: StringProperty(
          name="UI Tag",
      )
  
      def execute(self, context):
          workspace = context.workspace
          owner_id = workspace.owner_ids[self.owner_id]
          workspace.owner_ids.remove(owner_id)
          return {'FINISHED'}
  
  
  class WM_OT_tool_set_by_id(Operator):
      """Set the tool by name (for keymaps)"""
      bl_idname = "wm.tool_set_by_id"
      bl_label = "Set Tool by Name"
  
      name: StringProperty(
          name="Identifier",
          description="Identifier of the tool",
      )
      cycle: BoolProperty(
          name="Cycle",
          description="Cycle through tools in this group",
          default=False,
          options={'SKIP_SAVE'},
      )
      as_fallback: BoolProperty(
          name="Set Fallback",
          description="Set the fallback tool instead of the primary tool",
          default=False,
          options={'SKIP_SAVE', 'HIDDEN'},
      )
  
      space_type: rna_space_type_prop
  
      def execute(self, context):
          from bl_ui.space_toolsystem_common import (
              activate_by_id,
              activate_by_id_or_cycle,
          )
  
          if self.properties.is_property_set("space_type"):
              space_type = self.space_type
          else:
              space_type = context.space_data.type
  
          fn = activate_by_id_or_cycle if self.cycle else activate_by_id
          if fn(context, space_type, self.name, as_fallback=self.as_fallback):
              if self.as_fallback:
                  tool_settings = context.tool_settings
                  tool_settings.workspace_tool_type = 'FALLBACK'
              return {'FINISHED'}
          else:
              self.report({'WARNING'}, "Tool %r not found for space %r" % (self.name, space_type))
              return {'CANCELLED'}
  
  
  class WM_OT_tool_set_by_index(Operator):
      """Set the tool by index (for keymaps)"""
      bl_idname = "wm.tool_set_by_index"
      bl_label = "Set Tool by Index"
      index: IntProperty(
          name="Index in Toolbar",
          default=0,
      )
      cycle: BoolProperty(
          name="Cycle",
          description="Cycle through tools in this group",
          default=False,
          options={'SKIP_SAVE'},
      )
  
      expand: BoolProperty(
          description="Include tool subgroups",
          default=True,
          options={'SKIP_SAVE'},
      )
  
      as_fallback: BoolProperty(
          name="Set Fallback",
          description="Set the fallback tool instead of the primary",
          default=False,
          options={'SKIP_SAVE', 'HIDDEN'},
      )
  
      space_type: rna_space_type_prop
  
      def execute(self, context):
          from bl_ui.space_toolsystem_common import (
              activate_by_id,
              activate_by_id_or_cycle,
              item_from_index_active,
              item_from_flat_index,
          )
  
          if self.properties.is_property_set("space_type"):
              space_type = self.space_type
          else:
              space_type = context.space_data.type
  
          fn = item_from_flat_index if self.expand else item_from_index_active
          item = fn(context, space_type, self.index)
          if item is None:
              # Don't report, since the number of tools may change.
              return {'CANCELLED'}
  
          # Same as: WM_OT_tool_set_by_id
          fn = activate_by_id_or_cycle if self.cycle else activate_by_id
          if fn(context, space_type, item.idname, as_fallback=self.as_fallback):
              if self.as_fallback:
                  tool_settings = context.tool_settings
                  tool_settings.workspace_tool_type = 'FALLBACK'
              return {'FINISHED'}
          else:
              # Since we already have the tool, this can't happen.
              raise Exception("Internal error setting tool")
  
  
  class WM_OT_toolbar(Operator):
      bl_idname = "wm.toolbar"
      bl_label = "Toolbar"
  
      @classmethod
      def poll(cls, context):
          return context.space_data is not None
  
      @staticmethod
      def keymap_from_toolbar(context, space_type, use_fallback_keys=True, use_reset=True):
          from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
          from bl_keymap_utils import keymap_from_toolbar
  
          cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
          if cls is None:
              return None, None
  
          return cls, keymap_from_toolbar.generate(
              context,
              space_type,
              use_fallback_keys=use_fallback_keys,
              use_reset=use_reset,
          )
  
      def execute(self, context):
          space_type = context.space_data.type
          cls, keymap = self.keymap_from_toolbar(context, space_type)
          if keymap is None:
              return {'CANCELLED'}
  
          def draw_menu(popover, context):
              layout = popover.layout
              layout.operator_context = 'INVOKE_REGION_WIN'
              cls.draw_cls(layout, context, detect_layout=False, scale_y=1.0)
  
          wm = context.window_manager
          wm.popover(draw_menu, ui_units_x=8, keymap=keymap)
          return {'FINISHED'}
  
  
  class WM_OT_toolbar_fallback_pie(Operator):
      bl_idname = "wm.toolbar_fallback_pie"
      bl_label = "Fallback Tool Pie Menu"
  
      @classmethod
      def poll(cls, context):
          return context.space_data is not None
  
      def invoke(self, context, event):
          from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
          space_type = context.space_data.type
          cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
          if cls is None:
              return {'PASS_THROUGH'}
  
          # It's possible we don't have the fallback tool available.
          # This can happen in the image editor for example when there is no selection
          # in painting modes.
          item, _ = cls._tool_get_by_id(context, cls.tool_fallback_id)
          if item is None:
              print("Tool", cls.tool_fallback_id, "not active in", cls)
              return {'PASS_THROUGH'}
  
          def draw_cb(self, context):
              from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
              ToolSelectPanelHelper.draw_fallback_tool_items_for_pie_menu(self.layout, context)
  
          wm = context.window_manager
          wm.popup_menu_pie(draw_func=draw_cb, title="Fallback Tool", event=event)
          return {'FINISHED'}
  
  
  class WM_OT_toolbar_prompt(Operator):
      """Leader key like functionality for accessing tools"""
      bl_idname = "wm.toolbar_prompt"
      bl_label = "Toolbar Prompt"
  
      @staticmethod
      def _status_items_generate(cls, keymap, context):
          from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
  
          # The keymap doesn't have the same order the tools are declared in,
          # while we could support this, it's simpler to apply order here.
          tool_map_id_to_order = {}
          # Map the
          tool_map_id_to_label = {}
          for item in ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context)):
              if item is not None:
                  tool_map_id_to_label[item.idname] = item.label
                  tool_map_id_to_order[item.idname] = len(tool_map_id_to_order)
  
          status_items = []
  
          for item in keymap.keymap_items:
              name = item.name
              key_str = item.to_string()
              # These are duplicated from regular numbers.
              if key_str.startswith("Numpad "):
                  continue
              properties = item.properties
              idname = item.idname
              if idname == "wm.tool_set_by_id":
                  tool_idname = properties["name"]
                  name = tool_map_id_to_label[tool_idname]
                  name = name.replace("Annotate ", "")
              else:
                  continue
  
              status_items.append((tool_idname, name, item))
  
          status_items.sort(
              key=lambda a: tool_map_id_to_order[a[0]]
          )
          return status_items
  
      def modal(self, context, event):
          event_type = event.type
          event_value = event.value
  
          if event_type in {
                  'LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE',
                  'WHEELDOWNMOUSE', 'WHEELUPMOUSE', 'WHEELINMOUSE', 'WHEELOUTMOUSE',
                  'ESC',
          }:
              context.workspace.status_text_set(None)
              return {'CANCELLED', 'PASS_THROUGH'}
  
          keymap = self._keymap
          item = keymap.keymap_items.match_event(event)
          if item is not None:
              idname = item.idname
              properties = item.properties
              if idname == "wm.tool_set_by_id":
                  tool_idname = properties["name"]
                  bpy.ops.wm.tool_set_by_id(name=tool_idname)
  
              context.workspace.status_text_set(None)
              return {'FINISHED'}
  
          # Pressing entry even again exists, as long as it's not mapped to a key (for convenience).
          if event_type == self._init_event_type:
              if event_value == 'RELEASE':
                  if not (event.ctrl or event.alt or event.shift or event.oskey):
                      context.workspace.status_text_set(None)
                      return {'CANCELLED'}
  
          return {'RUNNING_MODAL'}
  
      def invoke(self, context, event):
          space_data = context.space_data
          if space_data is None:
              return {'CANCELLED'}
  
          space_type = space_data.type
          cls, keymap = WM_OT_toolbar.keymap_from_toolbar(
              context,
              space_type,
              use_fallback_keys=False,
              use_reset=False,
          )
          if (keymap is None) or (not keymap.keymap_items):
              return {'CANCELLED'}
  
          self._init_event_type = event.type
  
          # Strip Left/Right, since "Left Alt" isn't especially useful.
          init_event_type_as_text = self._init_event_type.title().split("_")
          if init_event_type_as_text[0] in {"Left", "Right"}:
              del init_event_type_as_text[0]
          init_event_type_as_text = " ".join(init_event_type_as_text)
  
          status_items = self._status_items_generate(cls, keymap, context)
  
          def status_text_fn(self, context):
  
              layout = self.layout
              if True:
                  box = layout.row(align=True).box()
                  box.scale_x = 0.8
                  box.label(text=init_event_type_as_text)
  
              flow = layout.grid_flow(columns=len(status_items), align=True, row_major=True)
              for _, name, item in status_items:
                  row = flow.row(align=True)
                  row.template_event_from_keymap_item(item, text=name)
  
          self._keymap = keymap
  
          context.workspace.status_text_set(status_text_fn)
  
          context.window_manager.modal_handler_add(self)
          return {'RUNNING_MODAL'}
  
  
  class BatchRenameAction(bpy.types.PropertyGroup):
      # category: StringProperty()
      type: EnumProperty(
          name="Operation",
          items=(
              ('REPLACE', "Find/Replace", "Replace text in the name"),
              ('SET', "Set Name", "Set a new name or prefix/suffix the existing one"),
              ('STRIP', "Strip Characters", "Strip leading/trailing text from the name"),
              ('CASE', "Change Case", "Change case of each name"),
          ),
      )
  
      # We could split these into sub-properties, however it's not so important.
  
      # type: 'SET'.
      set_name: StringProperty(name="Name")
      set_method: EnumProperty(
          name="Method",
          items=(
              ('NEW', "New", ""),
              ('PREFIX', "Prefix", ""),
              ('SUFFIX', "Suffix", ""),
          ),
          default='SUFFIX',
      )
  
      # type: 'STRIP'.
      strip_chars: EnumProperty(
          name="Strip Characters",
          options={'ENUM_FLAG'},
          items=(
              ('SPACE', "Spaces", ""),
              ('DIGIT', "Digits", ""),
              ('PUNCT', "Punctuation", ""),
          ),
      )
  
      # type: 'STRIP'.
      strip_part: EnumProperty(
          name="Strip Part",
          options={'ENUM_FLAG'},
          items=(
              ('START', "Start", ""),
              ('END', "End", ""),
          ),
      )
  
      # type: 'REPLACE'.
      replace_src: StringProperty(name="Find")
      replace_dst: StringProperty(name="Replace")
      replace_match_case: BoolProperty(name="Case Sensitive")
      use_replace_regex_src: BoolProperty(
          name="Regular Expression Find",
          description="Use regular expressions to match text in the 'Find' field"
      )
      use_replace_regex_dst: BoolProperty(
          name="Regular Expression Replace",
          description="Use regular expression for the replacement text (supporting groups)"
      )
  
      # type: 'CASE'.
      case_method: EnumProperty(
          name="Case",
          items=(
              ('UPPER', "Upper Case", ""),
              ('LOWER', "Lower Case", ""),
              ('TITLE', "Title Case", ""),
          ),
      )
  
      # Weak, add/remove as properties.
      op_add: BoolProperty()
      op_remove: BoolProperty()
  
  
  class WM_OT_batch_rename(Operator):
      bl_idname = "wm.batch_rename"
      bl_label = "Batch Rename"
  
      bl_options = {'UNDO'}
  
      data_type: EnumProperty(
          name="Type",
          items=(
              ('OBJECT', "Objects", ""),
              ('MATERIAL', "Materials", ""),
              None,
              # Enum identifiers are compared with 'object.type'.
              ('MESH', "Meshes", ""),
              ('CURVE', "Curves", ""),
              ('META', "Meta Balls", ""),
              ('ARMATURE', "Armatures", ""),
              ('LATTICE', "Lattices", ""),
              ('GPENCIL', "Grease Pencils", ""),
              ('CAMERA', "Cameras", ""),
              ('SPEAKER', "Speakers", ""),
              ('LIGHT_PROBE', "Light Probes", ""),
              None,
              ('BONE', "Bones", ""),
              ('NODE', "Nodes", ""),
              ('SEQUENCE_STRIP', "Sequence Strips", ""),
          ),
          description="Type of data to rename",
      )
  
      data_source: EnumProperty(
          name="Source",
          items=(
              ('SELECT', "Selected", ""),
              ('ALL', "All", ""),
          ),
      )
  
      actions: CollectionProperty(type=BatchRenameAction)
  
      @staticmethod
      def _data_from_context(context, data_type, only_selected, check_context=False):
  
          mode = context.mode
          scene = context.scene
          space = context.space_data
          space_type = None if (space is None) else space.type
  
          data = None
          if space_type == 'SEQUENCE_EDITOR':
              data_type_test = 'SEQUENCE_STRIP'
              if check_context:
                  return data_type_test
              if data_type == data_type_test:
                  data = (
                      # TODO, we don't have access to seqbasep, this won't work when inside metas.
                      [seq for seq in context.scene.sequence_editor.sequences_all if seq.select]
                      if only_selected else
                      context.scene.sequence_editor.sequences_all,
                      "name",
                      "Strip(s)",
                  )
          elif space_type == 'NODE_EDITOR':
              data_type_test = 'NODE'
              if check_context:
                  return data_type_test
              if data_type == data_type_test:
                  data = (
                      context.selected_nodes
                      if only_selected else
                      list(space.node_tree.nodes),
                      "name",
                      "Node(s)",
                  )
          else:
              if mode == 'POSE' or (mode == 'WEIGHT_PAINT' and context.pose_object):
                  data_type_test = 'BONE'
                  if check_context:
                      return data_type_test
                  if data_type == data_type_test:
                      data = (
                          [pchan.bone for pchan in context.selected_pose_bones]
                          if only_selected else
                          [pbone.bone for ob in context.objects_in_mode_unique_data for pbone in ob.pose.bones],
                          "name",
                          "Bone(s)",
                      )
              elif mode == 'EDIT_ARMATURE':
                  data_type_test = 'BONE'
                  if check_context:
                      return data_type_test
                  if data_type == data_type_test:
                      data = (
                          context.selected_editable_bones
                          if only_selected else
                          [ebone for ob in context.objects_in_mode_unique_data for ebone in ob.data.edit_bones],
                          "name",
                          "Edit Bone(s)",
                      )
  
          if check_context:
              return 'OBJECT'
  
          object_data_type_attrs_map = {
              'MESH': ("meshes", "Mesh(es)"),
              'CURVE': ("curves", "Curve(s)"),
              'META': ("metaballs", "Metaball(s)"),
              'ARMATURE': ("armatures", "Armature(s)"),
              'LATTICE': ("lattices", "Lattice(s)"),
              'GPENCIL': ("grease_pencils", "Grease Pencil(s)"),
              'CAMERA': ("cameras", "Camera(s)"),
              'SPEAKER': ("speakers", "Speaker(s)"),
              'LIGHT_PROBE': ("light_probes", "Light Probe(s)"),
          }
  
          # Finish with space types.
          if data is None:
  
              if data_type == 'OBJECT':
                  data = (
                      context.selected_editable_objects
                      if only_selected else
                      [id for id in bpy.data.objects if id.library is None],
                      "name",
                      "Object(s)",
                  )
              elif data_type == 'MATERIAL':
                  data = (
                      tuple(set(
                          slot.material
                          for ob in context.selected_objects
                          for slot in ob.material_slots
                          if slot.material is not None
                      ))
                      if only_selected else
                      [id for id in bpy.data.materials if id.library is None],
                      "name",
                      "Material(s)",
                  )
              elif data_type in object_data_type_attrs_map.keys():
                  attr, descr = object_data_type_attrs_map[data_type]
                  data = (
                      tuple(set(
                          id
                          for ob in context.selected_objects
                          if ob.type == data_type
                          for id in (ob.data,)
                          if id is not None and id.library is None
                      ))
                      if only_selected else
                      [id for id in getattr(bpy.data, attr) if id.library is None],
                      "name",
                      descr,
                  )
  
          return data
  
      @staticmethod
      def _apply_actions(actions, name):
          import string
          import re
  
          for action in actions:
              ty = action.type
              if ty == 'SET':
                  text = action.set_name
                  method = action.set_method
                  if method == 'NEW':
                      name = text
                  elif method == 'PREFIX':
                      name = text + name
                  elif method == 'SUFFIX':
                      name = name + text
                  else:
                      assert(0)
  
              elif ty == 'STRIP':
                  chars = action.strip_chars
                  chars_strip = (
                      "%s%s%s"
                  ) % (
                      string.punctuation if 'PUNCT' in chars else "",
                      string.digits if 'DIGIT' in chars else "",
                      " " if 'SPACE' in chars else "",
                  )
                  part = action.strip_part
                  if 'START' in part:
                      name = name.lstrip(chars_strip)
                  if 'END' in part:
                      name = name.rstrip(chars_strip)
  
              elif ty == 'REPLACE':
                  if action.use_replace_regex_src:
                      replace_src = action.replace_src
                      if action.use_replace_regex_dst:
                          replace_dst = action.replace_dst
                      else:
                          replace_dst = action.replace_dst.replace("\\", "\\\\")
                  else:
                      replace_src = re.escape(action.replace_src)
                      replace_dst = action.replace_dst.replace("\\", "\\\\")
                  name = re.sub(
                      replace_src,
                      replace_dst,
                      name,
                      flags=(
                          0 if action.replace_match_case else
                          re.IGNORECASE
                      ),
                  )
              elif ty == 'CASE':
                  method = action.case_method
                  if method == 'UPPER':
                      name = name.upper()
                  elif method == 'LOWER':
                      name = name.lower()
                  elif method == 'TITLE':
                      name = name.title()
                  else:
                      assert(0)
              else:
                  assert(0)
          return name
  
      def _data_update(self, context):
          only_selected = self.data_source == 'SELECT'
  
          self._data = self._data_from_context(context, self.data_type, only_selected)
          if self._data is None:
              self.data_type = self._data_from_context(context, None, False, check_context=True)
              self._data = self._data_from_context(context, self.data_type, only_selected)
  
          self._data_source_prev = self.data_source
          self._data_type_prev = self.data_type
  
      def draw(self, context):
          import re
  
          layout = self.layout
  
          split = layout.split(align=True)
          split.row(align=True).prop(self, "data_source", expand=True)
          split.prop(self, "data_type", text="")
  
          for action in self.actions:
              box = layout.box()
              split = box.split(factor=0.87)
  
              # Column 1: main content.
              col = split.column()
  
              # Label's width.
              fac = 0.25
  
              # Row 1: type.
              row = col.split(factor=fac)
              row.alignment = 'RIGHT'
              row.label(text="Type")
              row.prop(action, "type", text="")
  
              ty = action.type
              if ty == 'SET':
                  # Row 2: method.
                  row = col.split(factor=fac)
                  row.alignment = 'RIGHT'
                  row.label(text="Method")
                  row.row().prop(action, "set_method", expand=True)
  
                  # Row 3: name.
                  row = col.split(factor=fac)
                  row.alignment = 'RIGHT'
                  row.label(text="Name")
                  row.prop(action, "set_name", text="")
  
              elif ty == 'STRIP':
                  # Row 2: chars.
                  row = col.split(factor=fac)
                  row.alignment = 'RIGHT'
                  row.label(text="Characters")
                  row.row().prop(action, "strip_chars")
  
                  # Row 3: part.
                  row = col.split(factor=fac)
                  row.alignment = 'RIGHT'
                  row.label(text="Strip From")
                  row.row().prop(action, "strip_part")
  
              elif ty == 'REPLACE':
                  # Row 2: find.
                  row = col.split(factor=fac)
  
                  re_error_src = None
                  if action.use_replace_regex_src:
                      try:
                          re.compile(action.replace_src)
                      except Exception as ex:
                          re_error_src = str(ex)
                          row.alert = True
  
                  row.alignment = 'RIGHT'
                  row.label(text="Find")
                  sub = row.row(align=True)
                  sub.prop(action, "replace_src", text="")
                  sub.prop(action, "use_replace_regex_src", text="", icon='SORTBYEXT')
  
                  # Row.
                  if re_error_src is not None:
                      row = col.split(factor=fac)
                      row.label(text="")
                      row.alert = True
                      row.label(text=re_error_src)
  
                  # Row 3: replace.
                  row = col.split(factor=fac)
  
                  re_error_dst = None
                  if action.use_replace_regex_src:
                      if action.use_replace_regex_dst:
                          if re_error_src is None:
                              try:
                                  re.sub(action.replace_src, action.replace_dst, "")
                              except Exception as ex:
                                  re_error_dst = str(ex)
                                  row.alert = True
  
                  row.alignment = 'RIGHT'
                  row.label(text="Replace")
                  sub = row.row(align=True)
                  sub.prop(action, "replace_dst", text="")
                  subsub = sub.row(align=True)
                  subsub.active = action.use_replace_regex_src
                  subsub.prop(action, "use_replace_regex_dst", text="", icon='SORTBYEXT')
  
                  # Row.
                  if re_error_dst is not None:
                      row = col.split(factor=fac)
                      row.label(text="")
                      row.alert = True
                      row.label(text=re_error_dst)
  
                  # Row 4: case.
                  row = col.split(factor=fac)
                  row.label(text="")
                  row.prop(action, "replace_match_case")
  
              elif ty == 'CASE':
                  # Row 2: method.
                  row = col.split(factor=fac)
                  row.alignment = 'RIGHT'
                  row.label(text="Convert To")
                  row.row().prop(action, "case_method", expand=True)
  
              # Column 2: add-remove.
              row = split.split(align=True)
              row.prop(action, "op_remove", text="", icon='REMOVE')
              row.prop(action, "op_add", text="", icon='ADD')
  
          layout.label(text="Rename %d %s" % (len(self._data[0]), self._data[2]))
  
      def check(self, context):
          changed = False
          for i, action in enumerate(self.actions):
              if action.op_add:
                  action.op_add = False
                  self.actions.add()
                  if i + 2 != len(self.actions):
                      self.actions.move(len(self.actions) - 1, i + 1)
                  changed = True
                  break
              if action.op_remove:
                  action.op_remove = False
                  if len(self.actions) > 1:
                      self.actions.remove(i)
                  changed = True
                  break
  
          if (
                  (self._data_source_prev != self.data_source) or
                  (self._data_type_prev != self.data_type)
          ):
              self._data_update(context)
              changed = True
  
          return changed
  
      def execute(self, context):
          import re
  
          seq, attr, descr = self._data
  
          actions = self.actions
  
          # Sanitize actions.
          for action in actions:
              if action.use_replace_regex_src:
                  try:
                      re.compile(action.replace_src)
                  except Exception as ex:
                      self.report({'ERROR'}, "Invalid regular expression (find): " + str(ex))
                      return {'CANCELLED'}
  
                  if action.use_replace_regex_dst:
                      try:
                          re.sub(action.replace_src, action.replace_dst, "")
                      except Exception as ex:
                          self.report({'ERROR'}, "Invalid regular expression (replace): " + str(ex))
                          return {'CANCELLED'}
  
          total_len = 0
          change_len = 0
          for item in seq:
              name_src = getattr(item, attr)
              name_dst = self._apply_actions(actions, name_src)
              if name_src != name_dst:
                  setattr(item, attr, name_dst)
                  change_len += 1
              total_len += 1
  
          self.report({'INFO'}, "Renamed %d of %d %s" % (change_len, total_len, descr))
  
          return {'FINISHED'}
  
      def invoke(self, context, event):
  
          self._data_update(context)
  
          if not self.actions:
              self.actions.add()
          wm = context.window_manager
          return wm.invoke_props_dialog(self, width=400)
  
  
  class WM_MT_splash(Menu):
      bl_label = "Splash"
  
      def draw_setup(self, context):
          wm = context.window_manager
          # prefs = context.preferences
  
          layout = self.layout
  
          layout.operator_context = 'EXEC_DEFAULT'
  
          layout.label(text="Quick Setup")
  
          split = layout.split(factor=0.25)
          split.label()
          split = split.split(factor=2.0 / 3.0)
  
          col = split.column()
  
          sub = col.split(factor=0.35)
          row = sub.row()
          row.alignment = 'RIGHT'
          row.label(text="Language")
          prefs = context.preferences
          sub.prop(prefs.view, "language", text="")
  
          col.separator()
  
          sub = col.split(factor=0.35)
          row = sub.row()
          row.alignment = 'RIGHT'
          row.label(text="Shortcuts")
          text = bpy.path.display_name(wm.keyconfigs.active.name)
          if not text:
              text = "Blender"
          sub.menu("USERPREF_MT_keyconfigs", text=text)
  
          kc = wm.keyconfigs.active
          kc_prefs = kc.preferences
          has_select_mouse = hasattr(kc_prefs, "select_mouse")
          if has_select_mouse:
              sub = col.split(factor=0.35)
              row = sub.row()
              row.alignment = 'RIGHT'
              row.label(text="Select With")
              sub.row().prop(kc_prefs, "select_mouse", expand=True)
              has_select_mouse = True
  
          has_spacebar_action = hasattr(kc_prefs, "spacebar_action")
          if has_spacebar_action:
              sub = col.split(factor=0.35)
              row = sub.row()
              row.alignment = 'RIGHT'
              row.label(text="Spacebar")
              sub.row().prop(kc_prefs, "spacebar_action", expand=True)
              has_select_mouse = True
  
          col.separator()
  
          sub = col.split(factor=0.35)
          row = sub.row()
          row.alignment = 'RIGHT'
          row.label(text="Theme")
          label = bpy.types.USERPREF_MT_interface_theme_presets.bl_label
          if label == "Presets":
              label = "Blender Dark"
          sub.menu("USERPREF_MT_interface_theme_presets", text=label)
  
          # Keep height constant
          if not has_select_mouse:
              col.label()
          if not has_spacebar_action:
              col.label()
  
          layout.label()
  
          row = layout.row()
  
          sub = row.row()
          old_version = bpy.types.PREFERENCES_OT_copy_prev.previous_version()
          if bpy.types.PREFERENCES_OT_copy_prev.poll(context) and old_version:
              sub.operator("preferences.copy_prev", text="Load %d.%d Settings" % old_version)
              sub.operator("wm.save_userpref", text="Save New Settings")
          else:
              sub.label()
              sub.label()
              sub.operator("wm.save_userpref", text="Next")
  
          layout.separator()
          layout.separator()
  
      def draw(self, context):
          # Draw setup screen if no preferences have been saved yet.
          import os
  
          userconfig_path = bpy.utils.user_resource('CONFIG')
          userdef_path = os.path.join(userconfig_path, "userpref.blend")
  
          if not os.path.isfile(userdef_path):
              self.draw_setup(context)
              return
  
          # Pass
          layout = self.layout
          layout.operator_context = 'EXEC_DEFAULT'
          layout.emboss = 'PULLDOWN_MENU'
  
          split = layout.split()
  
          # Templates
          col1 = split.column()
          col1.label(text="New File")
  
          bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
  
          # Recent
          col2 = split.column()
          col2_title = col2.row()
  
          found_recent = col2.template_recent_files()
  
          if found_recent:
              col2_title.label(text="Recent Files")
          else:
  
              # Links if no recent files
              col2_title.label(text="Getting Started")
  
              col2.operator("wm.url_open_preset", text="Manual", icon='URL').type = 'MANUAL'
              col2.operator("wm.url_open_preset", text="Blender Website", icon='URL').type = 'BLENDER'
              col2.operator("wm.url_open_preset", text="Credits", icon='URL').type = 'CREDITS'
  
          layout.separator()
  
          split = layout.split()
  
          col1 = split.column()
          sub = col1.row()
          sub.operator_context = 'INVOKE_DEFAULT'
          sub.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
          col1.operator("wm.recover_last_session", icon='RECOVER_LAST')
  
          col2 = split.column()
  
          col2.operator("wm.url_open_preset", text="Release Notes", icon='URL').type = 'RELEASE_NOTES'
          col2.operator("wm.url_open_preset", text="Development Fund", icon='FUND').type = 'FUND'
  
          layout.separator()
          layout.separator()
  
  
  class WM_MT_splash_about(Menu):
      bl_label = "About"
  
      def draw(self, context):
  
          layout = self.layout
          layout.operator_context = 'EXEC_DEFAULT'
  
          split = layout.split(factor=0.65)
  
          col = split.column(align=True)
          col.scale_y = 0.8
          col.label(text=bpy.app.version_string, translate=False)
          col.separator(factor=2.5)
          col.label(text=iface_("Date: %s %s") % (bpy.app.build_commit_date.decode('utf-8', 'replace'),
                                                  bpy.app.build_commit_time.decode('utf-8', 'replace')), translate=False)
          col.label(text=iface_("Hash: %s") % bpy.app.build_hash.decode('ascii'), translate=False)
          col.label(text=iface_("Branch: %s") % bpy.app.build_branch.decode('utf-8', 'replace'), translate=False)
          col.separator(factor=2.0)
          col.label(text="Blender is free software")
          col.label(text="Licensed under the GNU General Public License")
  
          col = split.column(align=True)
          col.emboss = 'PULLDOWN_MENU'
          col.operator("wm.url_open_preset", text="Release Notes", icon='URL').type = 'RELEASE_NOTES'
          col.operator("wm.url_open_preset", text="Credits", icon='URL').type = 'CREDITS'
          col.operator("wm.url_open", text="License", icon='URL').url = "https://www.blender.org/about/license/"
          col.operator("wm.url_open_preset", text="Blender Website", icon='URL').type = 'BLENDER'
          col.operator("wm.url_open", text="Blender Store", icon='URL').url = "https://store.blender.org"
          col.operator("wm.url_open_preset", text="Development Fund", icon='FUND').type = 'FUND'
  
  
  class WM_OT_drop_blend_file(Operator):
      bl_idname = "wm.drop_blend_file"
      bl_label = "Handle dropped .blend file"
      bl_options = {'INTERNAL'}
  
      filepath: StringProperty()
  
      def invoke(self, context, _event):
          context.window_manager.popup_menu(self.draw_menu, title=bpy.path.basename(self.filepath), icon='QUESTION')
          return {'FINISHED'}
  
      def draw_menu(self, menu, _context):
          layout = menu.layout
  
          col = layout.column()
          col.operator_context = 'INVOKE_DEFAULT'
          props = col.operator("wm.open_mainfile", text="Open", icon='FILE_FOLDER')
          props.filepath = self.filepath
          props.display_file_selector = False
  
          layout.separator()
          col = layout.column()
          col.operator_context = 'INVOKE_DEFAULT'
          col.operator("wm.link", text="Link...", icon='LINK_BLEND').filepath = self.filepath
          col.operator("wm.append", text="Append...", icon='APPEND_BLEND').filepath = self.filepath
  
  
  classes = (
      WM_OT_context_collection_boolean_set,
      WM_OT_context_cycle_array,
      WM_OT_context_cycle_enum,
      WM_OT_context_cycle_int,
      WM_OT_context_menu_enum,
      WM_OT_context_modal_mouse,
      WM_OT_context_pie_enum,
      WM_OT_context_scale_float,
      WM_OT_context_scale_int,
      WM_OT_context_set_boolean,
      WM_OT_context_set_enum,
      WM_OT_context_set_float,
      WM_OT_context_set_id,
      WM_OT_context_set_int,
      WM_OT_context_set_string,
      WM_OT_context_set_value,
      WM_OT_context_toggle,
      WM_OT_context_toggle_enum,
      WM_OT_doc_view,
      WM_OT_doc_view_manual,
      WM_OT_drop_blend_file,
      WM_OT_operator_cheat_sheet,
      WM_OT_operator_pie_enum,
      WM_OT_path_open,
      WM_OT_properties_add,
      WM_OT_properties_context_change,
      WM_OT_properties_edit,
      WM_OT_properties_remove,
      WM_OT_sysinfo,
      WM_OT_owner_disable,
      WM_OT_owner_enable,
      WM_OT_url_open,
      WM_OT_url_open_preset,
      WM_OT_tool_set_by_id,
      WM_OT_tool_set_by_index,
      WM_OT_toolbar,
      WM_OT_toolbar_fallback_pie,
      WM_OT_toolbar_prompt,
      BatchRenameAction,
      WM_OT_batch_rename,
      WM_MT_splash,
      WM_MT_splash_about,
  )
  