# SPDX-FileCopyrightText: 2009-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

import bpy
from bpy.types import (
    Header,
    Menu,
    Panel,
)

from bl_ui.properties_data_grease_pencil import (
    GreasePencil_LayerMaskPanel,
    GreasePencil_LayerTransformPanel,
    GreasePencil_LayerRelationsPanel,
    GreasePencil_LayerAdjustmentsPanel,
    GreasePencil_LayerDisplayPanel,
)
from bl_ui.utils import (
    PlayheadSnappingPanel,
)

from rna_prop_ui import PropertyPanel

#######################################
# DopeSheet Filtering - Header Buttons

# used for DopeSheet, NLA, and Graph Editors


def dopesheet_filter(layout, context):
    dopesheet = context.space_data.dopesheet
    is_nla = context.area.type == 'NLA_EDITOR'
    is_action_editor = not is_nla and context.space_data.mode == 'ACTION'

    row = layout.row(align=True)
    if is_action_editor:
        row.prop(dopesheet, "show_only_slot_of_active_object", text="")
    row.prop(dopesheet, "show_only_selected", text="")
    row.prop(dopesheet, "show_hidden", text="")

    if is_nla:
        row.prop(dopesheet, "show_missing_nla", text="")
    else:  # graph and dopesheet editors - F-Curves and drivers only
        row.prop(dopesheet, "show_only_errors", text="")

#######################################
# Dope-sheet Filtering Popovers

# Generic Layout - Used as base for filtering popovers used in all animation editors
# Used for DopeSheet, NLA, and Graph Editors


class DopesheetFilterPopoverBase:
    bl_region_type = 'HEADER'
    bl_label = "Filters"

    # Generic = Affects all data-types.
    # XXX: Perhaps we want these to stay in the header instead, for easy/fast access
    @classmethod
    def draw_generic_filters(cls, context, layout):
        dopesheet = context.space_data.dopesheet
        is_nla = context.area.type == 'NLA_EDITOR'

        col = layout.column(align=True)
        col.prop(dopesheet, "show_only_selected", icon='NONE')
        col.prop(dopesheet, "show_hidden", icon='NONE')

        if is_nla:
            col.prop(dopesheet, "show_missing_nla", icon='NONE')
        else:  # Graph and dope-sheet editors - F-Curves and drivers only.
            col.prop(dopesheet, "show_only_errors", icon='NONE')

    # Name/Membership Filters
    # XXX: Perhaps these should just stay in the headers (exclusively)?
    @classmethod
    def draw_search_filters(cls, context, layout, generic_filters_only=False):
        dopesheet = context.space_data.dopesheet
        is_nla = context.area.type == 'NLA_EDITOR'

        col = layout.column(align=True)
        if not is_nla:
            row = col.row(align=True)
            row.prop(dopesheet, "filter_fcurve_name", text="")
        else:
            row = col.row(align=True)
            row.prop(dopesheet, "filter_text", text="")

        if (not generic_filters_only) and bpy.data.collections:
            col = layout.column(align=True)
            col.prop(dopesheet, "filter_collection", text="")

    # Standard = Present in all panels
    @classmethod
    def draw_standard_filters(cls, context, layout):
        dopesheet = context.space_data.dopesheet

        # datablock filters
        layout.label(text="Filter by Type:")
        flow = layout.grid_flow(row_major=True, columns=2, even_rows=False, align=False)

        flow.prop(dopesheet, "show_scenes", text="Scenes")
        flow.prop(dopesheet, "show_nodes", text="Node Trees")

        # object types
        if bpy.data.armatures:
            flow.prop(dopesheet, "show_armatures", text="Armatures")
        if bpy.data.cameras:
            flow.prop(dopesheet, "show_cameras", text="Cameras")
        if bpy.data.grease_pencils_v3:
            flow.prop(dopesheet, "show_gpencil", text="Grease Pencil Objects")
        if bpy.data.lights:
            flow.prop(dopesheet, "show_lights", text="Lights")
        if bpy.data.meshes:
            flow.prop(dopesheet, "show_meshes", text="Meshes")
        if bpy.data.curves:
            flow.prop(dopesheet, "show_curves", text="Curves")
        if bpy.data.lattices:
            flow.prop(dopesheet, "show_lattices", text="Lattices")
        if bpy.data.metaballs:
            flow.prop(dopesheet, "show_metaballs", text="Metaballs")
        if hasattr(bpy.data, "hair_curves") and bpy.data.hair_curves:
            flow.prop(dopesheet, "show_hair_curves", text="Hair Curves")
        if hasattr(bpy.data, "pointclouds") and bpy.data.pointclouds:
            flow.prop(dopesheet, "show_pointclouds", text="Point Clouds")
        if bpy.data.volumes:
            flow.prop(dopesheet, "show_volumes", text="Volumes")
        if bpy.data.lightprobes:
            flow.prop(dopesheet, "show_lightprobes", text="Light Probes")

        # data types
        flow.prop(dopesheet, "show_worlds", text="Worlds")
        if bpy.data.particles:
            flow.prop(dopesheet, "show_particles", text="Particles")
        if bpy.data.linestyles:
            flow.prop(dopesheet, "show_linestyles", text="Line Styles")
        if bpy.data.speakers:
            flow.prop(dopesheet, "show_speakers", text="Speakers")
        if bpy.data.materials:
            flow.prop(dopesheet, "show_materials", text="Materials")
        if bpy.data.textures:
            flow.prop(dopesheet, "show_textures", text="Textures")
        if bpy.data.shape_keys:
            flow.prop(dopesheet, "show_shapekeys", text="Shape Keys")
        if bpy.data.cache_files:
            flow.prop(dopesheet, "show_cache_files", text="Cache Files")
        if bpy.data.movieclips:
            flow.prop(dopesheet, "show_movieclips", text="Movie Clips")

        layout.separator()

        # Object Data Filters

        # TODO: Add per-channel/axis convenience toggles?
        split = layout.split()

        col = split.column()
        col.prop(dopesheet, "show_transforms", text="Transforms")

        col = split.column()
        col.prop(dopesheet, "show_modifiers", text="Modifiers")

        layout.separator()

        # performance-related options (users will mostly have these enabled)
        col = layout.column(align=True)
        col.label(text="Options:")
        col.prop(dopesheet, "use_datablock_sort", icon='NONE')


# Popover for Dope-sheet Editor(s) - Dope-sheet, Action, Shape-key, GPencil, Mask, etc.
class DOPESHEET_PT_filters(DopesheetFilterPopoverBase, Panel):
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_region_type = 'HEADER'
    bl_label = "Filters"

    def draw(self, context):
        layout = self.layout

        dopesheet = context.space_data.dopesheet
        ds_mode = context.space_data.mode

        layout.prop(dopesheet, "show_summary", text="Summary")

        DopesheetFilterPopoverBase.draw_generic_filters(context, layout)

        if ds_mode in {'DOPESHEET', 'ACTION', 'GPENCIL'}:
            layout.separator()
            generic_filters_only = ds_mode != 'DOPESHEET'
            DopesheetFilterPopoverBase.draw_search_filters(context, layout, generic_filters_only=generic_filters_only)

        if ds_mode == 'DOPESHEET':
            layout.separator()
            DopesheetFilterPopoverBase.draw_standard_filters(context, layout)


#######################################
# DopeSheet Editor - General/Standard UI

class DOPESHEET_HT_header(Header):
    bl_space_type = 'DOPESHEET_EDITOR'

    def draw(self, context):
        layout = self.layout

        st = context.space_data

        layout.template_header()

        if st.mode == 'TIMELINE':
            from bl_ui.space_time import (
                TIME_MT_editor_menus,
                TIME_HT_editor_buttons,
            )
            TIME_MT_editor_menus.draw_collapsible(context, layout)
            TIME_HT_editor_buttons.draw_header(context, layout)
        else:
            layout.prop(st, "ui_mode", text="")

            DOPESHEET_MT_editor_menus.draw_collapsible(context, layout)
            DOPESHEET_HT_editor_buttons.draw_header(context, layout)


class DOPESHEET_PT_playhead_snapping(PlayheadSnappingPanel, Panel):
    bl_space_type = 'DOPESHEET_EDITOR'


# Header for "normal" dopesheet editor modes (e.g. Dope Sheet, Action, Shape Keys, etc.)
class DOPESHEET_HT_editor_buttons:

    @classmethod
    def draw_header(cls, context, layout):
        st = context.space_data
        tool_settings = context.tool_settings

        if st.mode in {'ACTION', 'SHAPEKEY'} and context.object:
            layout.separator_spacer()
            cls._draw_action_selector(context, layout)

        # Layer management
        if st.mode == 'GPENCIL':
            ob = context.active_object

            enable_but = ob is not None and ob.type == 'GREASEPENCIL'

            row = layout.row(align=True)
            row.enabled = enable_but
            row.operator("grease_pencil.layer_add", icon='ADD', text="")
            row.operator("grease_pencil.layer_remove", icon='REMOVE', text="")
            row.menu("GREASE_PENCIL_MT_grease_pencil_add_layer_extra", icon='DOWNARROW_HLT', text="")

            row = layout.row(align=True)
            row.enabled = enable_but
            row.operator("anim.channels_move", icon='TRIA_UP', text="").direction = 'UP'
            row.operator("anim.channels_move", icon='TRIA_DOWN', text="").direction = 'DOWN'

            row = layout.row(align=True)
            row.enabled = enable_but
            row.operator("grease_pencil.layer_isolate", icon='RESTRICT_VIEW_ON', text="").affect_visibility = True
            row.operator("grease_pencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False

        layout.separator_spacer()

        if st.mode == 'DOPESHEET':
            dopesheet_filter(layout, context)
        elif st.mode == 'ACTION':
            dopesheet_filter(layout, context)
        elif st.mode == 'GPENCIL':
            row = layout.row(align=True)
            row.prop(st.dopesheet, "show_only_selected", text="")
            row.prop(st.dopesheet, "show_hidden", text="")

        layout.popover(
            panel="DOPESHEET_PT_filters",
            text="",
            icon='FILTER',
        )

        # Grease Pencil mode doesn't need snapping, as it's frame-aligned only
        if st.mode != 'GPENCIL':
            row = layout.row(align=True)
            row.prop(tool_settings, "use_snap_anim", text="")
            sub = row.row(align=True)
            sub.popover(
                panel="DOPESHEET_PT_snapping",
                text="",
            )

        layout.popover(panel="DOPESHEET_PT_playhead_snapping")

        row = layout.row(align=True)
        row.prop(tool_settings, "use_proportional_action", text="", icon_only=True)
        sub = row.row(align=True)
        sub.active = tool_settings.use_proportional_action
        sub.prop_with_popover(
            tool_settings,
            "proportional_edit_falloff",
            text="",
            icon_only=True,
            panel="DOPESHEET_PT_proportional_edit",
        )

    @classmethod
    def _draw_action_selector(cls, context, layout):
        animated_id = cls._get_animated_id(context)
        if not animated_id:
            return

        row = layout.row()
        if animated_id.animation_data and animated_id.animation_data.use_tweak_mode:
            row.enabled = False

        row.template_action(animated_id, new="action.new", unlink="action.unlink")

        adt = animated_id and animated_id.animation_data
        if not adt or not adt.action or not adt.action.is_action_layered:
            return

        # Store the animated ID in the context, so that the new/unlink operators
        # have access to it.
        row.context_pointer_set("animated_id", animated_id)
        row.template_search(
            adt, "action_slot",
            adt, "action_suitable_slots",
            new="anim.slot_new_for_id",
            unlink="anim.slot_unassign_from_id",
        )

    @staticmethod
    def _get_animated_id(context):
        st = context.space_data
        match st.mode:
            case 'ACTION':
                return context.object
            case 'SHAPEKEY':
                return getattr(context.object.data, "shape_keys", None)
            case _:
                print("Dope Sheet mode '{:s}' not expected to have an Action selector".format(st.mode))
                return context.object


class DOPESHEET_PT_snapping(Panel):
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_region_type = 'HEADER'
    bl_label = "Snapping"

    def draw(self, context):
        layout = self.layout
        col = layout.column()
        col.label(text="Snap To")
        tool_settings = context.tool_settings
        col.prop(tool_settings, "snap_anim_element", expand=True)
        if tool_settings.snap_anim_element != 'MARKER':
            col.prop(tool_settings, "use_snap_time_absolute")


class DOPESHEET_PT_proportional_edit(Panel):
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_region_type = 'HEADER'
    bl_label = "Proportional Editing"
    bl_ui_units_x = 8

    def draw(self, context):
        layout = self.layout
        tool_settings = context.tool_settings
        col = layout.column()
        col.active = tool_settings.use_proportional_action

        col.prop(tool_settings, "proportional_edit_falloff", expand=True)
        col.prop(tool_settings, "proportional_size")


class DOPESHEET_MT_editor_menus(Menu):
    bl_idname = "DOPESHEET_MT_editor_menus"
    bl_label = ""

    def draw(self, context):
        layout = self.layout
        st = context.space_data

        layout.menu("DOPESHEET_MT_view")
        layout.menu("DOPESHEET_MT_select")
        if st.show_markers:
            layout.menu("DOPESHEET_MT_marker")

        if st.mode == 'DOPESHEET' or (st.mode == 'ACTION' and st.action is not None):
            layout.menu("DOPESHEET_MT_channel")
        elif st.mode == 'GPENCIL':
            layout.menu("DOPESHEET_MT_gpencil_channel")

        layout.menu("DOPESHEET_MT_key")

        if st.mode in {'ACTION', 'SHAPEKEY'} and st.action is not None:
            layout.menu("DOPESHEET_MT_action")


class DOPESHEET_MT_view(Menu):
    bl_label = "View"

    def draw(self, context):
        layout = self.layout

        st = context.space_data

        layout.prop(st, "show_region_ui")
        layout.prop(st, "show_region_hud")
        layout.prop(st, "show_region_channels")
        layout.separator()

        layout.operator("action.view_selected")
        layout.operator("action.view_all")
        if context.scene.use_preview_range:
            layout.operator("anim.scene_range_frame", text="Frame Preview Range")
        else:
            layout.operator("anim.scene_range_frame", text="Frame Scene Range")
        layout.operator("action.view_frame")
        layout.separator()

        layout.prop(st.dopesheet, "use_multi_word_filter", text="Multi-Word Match Search")
        layout.separator()

        layout.prop(st, "use_realtime_update")

        # Sliders are always shown in the Shape Key Editor regardless of this setting.
        col = layout.column()
        col.active = context.space_data.mode != 'SHAPEKEY'
        col.prop(st, "show_sliders")

        layout.prop(st, "show_interpolation")
        layout.prop(st, "show_extremes")
        layout.prop(st, "use_auto_merge_keyframes")
        layout.separator()

        layout.prop(st, "show_markers")
        layout.prop(st, "show_seconds")
        layout.prop(st, "show_locked_time")
        layout.separator()

        layout.operator("anim.previewrange_set")
        layout.operator("anim.previewrange_clear")
        layout.operator("action.previewrange_set")
        layout.separator()

        # Add this to show key-binding (reverse action in dope-sheet).
        props = layout.operator("wm.context_set_enum", text="Toggle Graph Editor", icon='GRAPH')
        props.data_path = "area.type"
        props.value = 'GRAPH_EDITOR'
        layout.separator()

        layout.menu("INFO_MT_area")


class DOPESHEET_MT_view_pie(Menu):
    bl_label = "View"

    def draw(self, context):
        layout = self.layout

        pie = layout.menu_pie()
        pie.operator("action.view_all")
        pie.operator("action.view_selected", icon='ZOOM_SELECTED')
        pie.operator("action.view_frame")
        if context.scene.use_preview_range:
            pie.operator("anim.scene_range_frame", text="Frame Preview Range")
        else:
            pie.operator("anim.scene_range_frame", text="Frame Scene Range")


class DOPESHEET_MT_select(Menu):
    bl_label = "Select"

    def draw(self, context):
        layout = self.layout

        layout.operator("action.select_all", text="All").action = 'SELECT'
        layout.operator("action.select_all", text="None").action = 'DESELECT'
        layout.operator("action.select_all", text="Invert").action = 'INVERT'

        layout.separator()
        layout.operator("action.select_box").axis_range = False
        layout.operator("action.select_box", text="Box Select (Axis Range)").axis_range = True
        layout.operator("action.select_circle")
        layout.operator_menu_enum("action.select_lasso", "mode")

        # FIXME: grease pencil mode isn't supported for these yet, so skip for that mode only
        if context.space_data.mode != 'GPENCIL':
            layout.separator()
            layout.operator("action.select_more", text="More")
            layout.operator("action.select_less", text="Less")

            layout.separator()
            layout.operator("action.select_linked")

        layout.separator()
        layout.operator("action.select_column", text="Columns on Selected Keys").mode = 'KEYS'
        layout.operator("action.select_column", text="Column on Current Frame").mode = 'CFRA'

        layout.operator("action.select_column", text="Columns on Selected Markers").mode = 'MARKERS_COLUMN'
        layout.operator("action.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN'

        layout.separator()
        props = layout.operator("action.select_leftright", text="Before Current Frame")
        props.extend = False
        props.mode = 'LEFT'
        props = layout.operator("action.select_leftright", text="After Current Frame")
        props.extend = False
        props.mode = 'RIGHT'


class DOPESHEET_MT_marker(Menu):
    bl_label = "Marker"

    def draw(self, context):
        layout = self.layout

        from bl_ui.space_time import marker_menu_generic
        marker_menu_generic(layout, context)

        st = context.space_data

        if st.mode in {'ACTION', 'SHAPEKEY'} and st.action:
            layout.separator()
            layout.prop(st, "show_pose_markers")

            if st.show_pose_markers is False:
                layout.operator("action.markers_make_local")

        layout.prop(st, "use_marker_sync")

#######################################
# Keyframe Editing


class DOPESHEET_MT_channel(Menu):
    bl_label = "Channel"

    def draw(self, _context):
        layout = self.layout

        layout.operator_context = 'INVOKE_REGION_CHANNELS'

        layout.operator("anim.channels_delete")
        layout.operator("action.clean", text="Clean Channels").channels = True

        layout.separator()
        layout.operator("anim.channels_group")
        layout.operator("anim.channels_ungroup")

        layout.separator()
        layout.operator_menu_enum("anim.channels_setting_toggle", "type")
        layout.operator_menu_enum("anim.channels_setting_enable", "type")
        layout.operator_menu_enum("anim.channels_setting_disable", "type")

        layout.separator()
        layout.operator("anim.channels_editable_toggle")
        layout.operator_menu_enum("action.extrapolation_type", "type", text="Extrapolation Mode")

        layout.separator()
        layout.operator("anim.channels_expand")
        layout.operator("anim.channels_collapse")

        layout.separator()
        layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")

        layout.separator()
        layout.operator("anim.channels_fcurves_enable")

        layout.separator()
        layout.operator("anim.channels_bake")

        layout.separator()
        layout.operator("anim.channels_view_selected")


class DOPESHEET_MT_action(Menu):
    bl_label = "Action"

    def draw(self, context):
        layout = self.layout
        layout.operator("anim.merge_animation")
        layout.operator("anim.separate_slots")

        layout.separator()
        layout.operator("anim.slot_channels_move_to_new_action")

        layout.separator()
        layout.operator("action.push_down", text="Push Down Action", icon='NLA_PUSHDOWN')
        layout.operator("action.stash", text="Stash Action", icon='FREEZE')


class DOPESHEET_MT_key(Menu):
    bl_label = "Key"

    def draw(self, context):
        layout = self.layout
        ob = context.active_object

        layout.menu("DOPESHEET_MT_key_transform", text="Transform")

        layout.operator_menu_enum("action.snap", "type", text="Snap")
        layout.operator_menu_enum("action.mirror", "type", text="Mirror")

        layout.separator()
        layout.operator("action.keyframe_insert")

        layout.separator()
        layout.operator("action.frame_jump")

        layout.separator()
        layout.operator("action.copy")
        layout.operator("action.paste")
        layout.operator("action.paste", text="Paste Flipped").flipped = True
        layout.operator("action.duplicate_move")
        layout.operator("action.delete")
        if ob and ob.type == 'GREASEPENCIL':
            layout.operator("grease_pencil.delete_breakdown")

        layout.separator()
        layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")
        layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
        layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
        layout.operator_menu_enum("action.easing_type", "type", text="Easing Mode")

        layout.separator()
        layout.operator("action.clean").channels = False
        layout.operator("action.bake_keys")

        layout.separator()
        layout.operator("graph.euler_filter", text="Discontinuity (Euler) Filter")


class DOPESHEET_MT_key_transform(Menu):
    bl_label = "Transform"

    def draw(self, _context):
        layout = self.layout

        layout.operator("transform.transform", text="Move").mode = 'TIME_TRANSLATE'
        layout.operator("transform.transform", text="Extend").mode = 'TIME_EXTEND'
        layout.operator("transform.transform", text="Slide").mode = 'TIME_SLIDE'
        layout.operator("transform.transform", text="Scale").mode = 'TIME_SCALE'


class DopesheetActionPanelBase:
    bl_region_type = 'UI'
    bl_label = "Action"

    @classmethod
    def draw_generic_panel(cls, _context, layout, action):
        layout.label(text=action.name, icon='ACTION', translate=False)

        layout.prop(action, "use_frame_range")

        col = layout.column()
        col.active = action.use_frame_range

        row = col.row(align=True)
        row.prop(action, "frame_start", text="Start")
        row.prop(action, "frame_end", text="End")

        col.prop(action, "use_cyclic")


class DOPESHEET_PT_custom_props_action(PropertyPanel, Panel):
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_category = "Action"
    bl_region_type = 'UI'
    bl_context = "data"
    _context_path = "active_action"
    _property_type = bpy.types.Action

    @classmethod
    def poll(cls, context):
        return bool(context.active_action)


class DOPESHEET_PT_action(DopesheetActionPanelBase, Panel):
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_category = "Action"

    @classmethod
    def poll(cls, context):
        return bool(context.active_action)

    def draw(self, context):
        action = context.active_action
        self.draw_generic_panel(context, self.layout, action)


class DOPESHEET_PT_action_slot(Panel):
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_region_type = 'UI'
    bl_category = "Action"
    bl_label = "Slot"

    @classmethod
    def poll(cls, context):
        action = context.active_action
        return bool(action and action.slots.active)

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True
        layout.use_property_decorate = False

        action = context.active_action
        slot = action.slots.active

        layout.prop(slot, "name_display", text="Name")

        # Draw the ID type of the slot.
        try:
            enum_items = slot.bl_rna.properties['target_id_type'].enum_items
            idtype_label = enum_items[slot.target_id_type].name
        except (KeyError, IndexError, AttributeError) as ex:
            idtype_label = str(ex)

        split = layout.split(factor=0.4)
        split.alignment = 'RIGHT'
        split.label(text="Type")
        split.alignment = 'LEFT'

        split.label(text=idtype_label, icon_value=slot.target_id_type_icon)


#######################################
# Grease Pencil Editing

class DOPESHEET_MT_gpencil_channel(Menu):
    bl_label = "Channel"

    def draw(self, _context):
        layout = self.layout

        layout.operator_context = 'INVOKE_REGION_CHANNELS'

        layout.operator("anim.channels_delete")

        layout.separator()
        layout.operator("anim.channels_setting_toggle")
        layout.operator("anim.channels_setting_enable")
        layout.operator("anim.channels_setting_disable")

        layout.separator()
        layout.operator("anim.channels_editable_toggle")

        # XXX: to be enabled when these are ready for use!
        # layout.separator()
        # layout.operator("anim.channels_expand")
        # layout.operator("anim.channels_collapse")

        layout.separator()
        layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")

        layout.separator()
        layout.operator("anim.channels_view_selected")


class DOPESHEET_MT_delete(Menu):
    bl_label = "Delete"

    def draw(self, _context):
        layout = self.layout

        layout.operator("action.delete")

        layout.separator()

        layout.operator("action.clean").channels = False
        layout.operator("action.clean", text="Clean Channels").channels = True


class DOPESHEET_MT_context_menu(Menu):
    bl_label = "Dope Sheet"

    def draw(self, context):
        layout = self.layout
        st = context.space_data

        layout.operator_context = 'INVOKE_DEFAULT'

        layout.operator("action.copy", text="Copy", icon='COPYDOWN')
        layout.operator("action.paste", text="Paste", icon='PASTEDOWN')
        layout.operator("action.paste", text="Paste Flipped", icon='PASTEFLIPDOWN').flipped = True

        layout.separator()

        layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")

        if st.mode != 'GPENCIL':
            layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
            layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
            layout.operator_menu_enum("action.easing_type", "type", text="Easing Mode")

        layout.separator()

        layout.operator("action.keyframe_insert").type = 'SEL'
        layout.operator("action.duplicate_move")

        if st.mode == 'GPENCIL':
            layout.separator()
            layout.operator("grease_pencil.delete_breakdown")

        layout.operator_context = 'EXEC_REGION_WIN'
        layout.operator("action.delete")

        layout.separator()

        layout.operator_menu_enum("action.mirror", "type", text="Mirror")
        layout.operator_menu_enum("action.snap", "type", text="Snap")


class DOPESHEET_MT_channel_context_menu(Menu):
    bl_label = "Channel"

    def draw(self, context):
        layout = self.layout

        # This menu is used from the graph editor too.
        is_graph_editor = context.area.type == 'GRAPH_EDITOR'

        layout.operator_context = 'INVOKE_REGION_CHANNELS'

        layout.separator()
        layout.operator("anim.channels_view_selected")

        layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
        layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
        layout.separator()
        layout.operator("anim.channels_setting_enable", text="Protect Channels").type = 'PROTECT'
        layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type = 'PROTECT'

        layout.separator()
        layout.operator("anim.channels_group")
        layout.operator("anim.channels_ungroup")

        layout.separator()
        layout.operator("anim.channels_editable_toggle")

        if is_graph_editor:
            operator = "graph.extrapolation_type"
        else:
            operator = "action.extrapolation_type"
        layout.operator_menu_enum(operator, "type", text="Extrapolation Mode")

        if is_graph_editor:
            layout.operator_menu_enum("graph.fmodifier_add", "type", text="Add F-Curve Modifier").only_active = False
            layout.separator()
            layout.operator("graph.hide", text="Hide Selected Curves").unselected = False
            layout.operator("graph.hide", text="Hide Unselected Curves").unselected = True
            layout.operator("graph.reveal")

        layout.separator()
        layout.operator("anim.channels_expand")
        layout.operator("anim.channels_collapse")

        layout.separator()
        layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")

        layout.separator()

        layout.operator("anim.channels_delete")

        if is_graph_editor and context.space_data.mode == 'DRIVERS':
            layout.operator("graph.driver_delete_invalid")


class DOPESHEET_MT_snap_pie(Menu):
    bl_label = "Snap"

    def draw(self, _context):
        layout = self.layout
        pie = layout.menu_pie()

        pie.operator("action.snap", text="Selection to Current Frame").type = 'CFRA'
        pie.operator("action.snap", text="Selection to Nearest Frame").type = 'NEAREST_FRAME'
        pie.operator("action.snap", text="Selection to Nearest Second").type = 'NEAREST_SECOND'
        pie.operator("action.snap", text="Selection to Nearest Marker").type = 'NEAREST_MARKER'


class LayersDopeSheetPanel:
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_region_type = 'UI'
    bl_category = "View"

    @classmethod
    def poll(cls, context):
        st = context.space_data
        ob = context.object
        if st.mode != 'GPENCIL' or ob is None or ob.type != 'GREASEPENCIL':
            return False

        gpd = ob.data
        gpl = gpd.layers.active
        if gpl:
            return True

        return False


class GreasePencilLayersDopeSheetPanel:
    bl_space_type = 'DOPESHEET_EDITOR'
    bl_region_type = 'UI'
    bl_category = "View"

    @classmethod
    def poll(cls, context):
        st = context.space_data
        ob = context.object
        if st.mode != 'GPENCIL' or ob is None or ob.type != 'GREASEPENCIL':
            return False

        grease_pencil = ob.data
        active_layer = grease_pencil.layers.active
        if active_layer:
            return True

        return False


class DOPESHEET_PT_grease_pencil_mode(GreasePencilLayersDopeSheetPanel, Panel):
    bl_label = "Layer"

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True
        layout.use_property_decorate = False

        ob = context.object
        grease_pencil = ob.data
        active_layer = grease_pencil.layers.active

        if active_layer:
            row = layout.row(align=True)
            row.prop(active_layer, "blend_mode", text="Blend")

            row = layout.row(align=True)
            row.prop(active_layer, "opacity", text="Opacity", slider=True)

            row = layout.row(align=True)
            row.prop(active_layer, "use_lights", text="Lights")


class DOPESHEET_PT_grease_pencil_layer_masks(GreasePencilLayersDopeSheetPanel, GreasePencil_LayerMaskPanel, Panel):
    bl_label = "Masks"
    bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
    bl_options = {'DEFAULT_CLOSED'}


class DOPESHEET_PT_grease_pencil_layer_transform(
        GreasePencilLayersDopeSheetPanel,
        GreasePencil_LayerTransformPanel,
        Panel,
):
    bl_label = "Transform"
    bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
    bl_options = {'DEFAULT_CLOSED'}


class DOPESHEET_PT_grease_pencil_layer_relations(
        GreasePencilLayersDopeSheetPanel,
        GreasePencil_LayerRelationsPanel,
        Panel,
):
    bl_label = "Relations"
    bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
    bl_options = {'DEFAULT_CLOSED'}


class DOPESHEET_PT_grease_pencil_layer_adjustments(
        GreasePencilLayersDopeSheetPanel,
        GreasePencil_LayerAdjustmentsPanel,
        Panel,
):
    bl_label = "Adjustments"
    bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
    bl_options = {'DEFAULT_CLOSED'}


class DOPESHEET_PT_grease_pencil_layer_display(
        GreasePencilLayersDopeSheetPanel,
        GreasePencil_LayerDisplayPanel,
        Panel,
):
    bl_label = "Display"
    bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
    bl_options = {'DEFAULT_CLOSED'}


classes = (
    DOPESHEET_HT_header,
    DOPESHEET_PT_proportional_edit,
    DOPESHEET_MT_editor_menus,
    DOPESHEET_MT_view,
    DOPESHEET_MT_select,
    DOPESHEET_MT_marker,
    DOPESHEET_MT_channel,
    DOPESHEET_MT_action,
    DOPESHEET_MT_key,
    DOPESHEET_MT_key_transform,
    DOPESHEET_MT_gpencil_channel,
    DOPESHEET_MT_delete,
    DOPESHEET_MT_context_menu,
    DOPESHEET_MT_channel_context_menu,
    DOPESHEET_MT_snap_pie,
    DOPESHEET_MT_view_pie,
    DOPESHEET_PT_filters,
    DOPESHEET_PT_action,
    DOPESHEET_PT_action_slot,
    DOPESHEET_PT_custom_props_action,
    DOPESHEET_PT_snapping,
    DOPESHEET_PT_grease_pencil_mode,
    DOPESHEET_PT_grease_pencil_layer_masks,
    DOPESHEET_PT_grease_pencil_layer_transform,
    DOPESHEET_PT_grease_pencil_layer_adjustments,
    DOPESHEET_PT_grease_pencil_layer_relations,
    DOPESHEET_PT_grease_pencil_layer_display,
    DOPESHEET_PT_playhead_snapping,
)

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