/*
 * Copyright (C) 2023 zhuyaliang.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include "mate-image-menu-item.h"

struct _MateImageMenuItem
{
    GtkMenuItem  menu_item;
    GtkWidget   *image;
    gchar       *label;
};

enum {
    PROP_0,
    PROP_IMAGE,
};

G_DEFINE_TYPE (MateImageMenuItem, mate_image_menu_item, GTK_TYPE_MENU_ITEM)

static void
mate_image_menu_item_destroy (GtkWidget *widget)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (widget);

    if (image_menu_item->image)
        gtk_container_remove (GTK_CONTAINER (image_menu_item),
                              image_menu_item->image);

    GTK_WIDGET_CLASS (mate_image_menu_item_parent_class)->destroy (widget);
}

static void
mate_image_menu_item_get_preferred_width (GtkWidget *widget,
                                          gint      *minimum,
                                          gint      *natural)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (widget);
    GtkPackDirection pack_dir;
    GtkWidget *parent;

    parent = gtk_widget_get_parent (widget);

    if (GTK_IS_MENU_BAR (parent))
        pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
    else
        pack_dir = GTK_PACK_DIRECTION_LTR;

    GTK_WIDGET_CLASS (mate_image_menu_item_parent_class)->get_preferred_width (widget, minimum, natural);

    if ((pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT) &&
        image_menu_item->image &&
        gtk_widget_get_visible (image_menu_item->image))
    {
        gint child_minimum, child_natural;

        gtk_widget_get_preferred_width (image_menu_item->image, &child_minimum, &child_natural);

        *minimum = MAX (*minimum, child_minimum);
        *natural = MAX (*natural, child_natural);
    }
}

static void
mate_image_menu_item_get_preferred_height (GtkWidget *widget,
                                           gint      *minimum,
                                           gint      *natural)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (widget);
    gint child_height = 0;
    GtkPackDirection pack_dir;
    GtkWidget *parent;

    parent = gtk_widget_get_parent (widget);

    if (GTK_IS_MENU_BAR (parent))
        pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
    else
        pack_dir = GTK_PACK_DIRECTION_LTR;

    if (image_menu_item->image && gtk_widget_get_visible (image_menu_item->image))
    {
        GtkRequisition child_requisition;

        gtk_widget_get_preferred_size (image_menu_item->image, &child_requisition, NULL);

        child_height = child_requisition.height;
    }

    GTK_WIDGET_CLASS (mate_image_menu_item_parent_class)->get_preferred_height (widget, minimum, natural);

    if (pack_dir == GTK_PACK_DIRECTION_RTL || pack_dir == GTK_PACK_DIRECTION_LTR)
    {
        *minimum = MAX (*minimum, child_height);
        *natural = MAX (*natural, child_height);
    }
}

static void
mate_image_menu_item_get_preferred_height_for_width (GtkWidget *widget,
                                                     gint       width,
                                                     gint      *minimum,
                                                     gint      *natural)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (widget);
    gint child_height = 0;
    GtkPackDirection pack_dir;
    GtkWidget *parent;

    parent = gtk_widget_get_parent (widget);

    if (GTK_IS_MENU_BAR (parent))
        pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
    else
        pack_dir = GTK_PACK_DIRECTION_LTR;

    if (image_menu_item->image && gtk_widget_get_visible (image_menu_item->image))
    {
        GtkRequisition child_requisition;

        gtk_widget_get_preferred_size (image_menu_item->image, &child_requisition, NULL);

        child_height = child_requisition.height;
    }

    GTK_WIDGET_CLASS (mate_image_menu_item_parent_class)->get_preferred_height_for_width (widget, width, minimum, natural);

    if (pack_dir == GTK_PACK_DIRECTION_RTL || pack_dir == GTK_PACK_DIRECTION_LTR)
    {
        *minimum = MAX (*minimum, child_height);
        *natural = MAX (*natural, child_height);
    }
}

static void
mate_image_menu_item_size_allocate (GtkWidget     *widget,
                                    GtkAllocation *allocation)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (widget);
    GtkAllocation widget_allocation;
    GtkRequisition image_requisition;
    GtkPackDirection pack_dir;
    GtkTextDirection text_dir;
    GtkAllocation image_allocation;
    GtkStyleContext *context;
    GtkStateFlags state;
    GtkBorder padding;
    GtkWidget *parent;
    gint toggle_size;
    gint x;
    gint y;

    parent = gtk_widget_get_parent (widget);

    if (GTK_IS_MENU_BAR (parent))
        pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
    else
        pack_dir = GTK_PACK_DIRECTION_LTR;

    GTK_WIDGET_CLASS (mate_image_menu_item_parent_class)->size_allocate (widget, allocation);

    if (!image_menu_item->image || !gtk_widget_get_visible (image_menu_item->image))
        return;

    gtk_widget_get_allocation (widget, &widget_allocation);
    gtk_widget_get_preferred_size (image_menu_item->image, &image_requisition, NULL);

    context = gtk_widget_get_style_context (widget);
    state = gtk_style_context_get_state (context);
    gtk_style_context_get_padding (context, state, &padding);

    toggle_size = 0;
    gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (image_menu_item), &toggle_size);

    text_dir = gtk_widget_get_direction (widget);

    if (pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL)
    {
        if ((text_dir == GTK_TEXT_DIR_LTR) == (pack_dir == GTK_PACK_DIRECTION_LTR))
        {
            x = padding.left + (toggle_size - image_requisition.width) / 2;
        }
        else
        {
            x = widget_allocation.width - padding.right - toggle_size +
                (toggle_size - image_requisition.width) / 2;
        }

        y = (widget_allocation.height - image_requisition.height) / 2;
    }
    else
    {
        if ((text_dir == GTK_TEXT_DIR_LTR) == (pack_dir == GTK_PACK_DIRECTION_TTB))
        {
            y = padding.top + (toggle_size - image_requisition.height) / 2;
        }
        else
        {
            y = widget_allocation.height - padding.bottom - toggle_size +
                (toggle_size - image_requisition.height) / 2;
        }

        x = (widget_allocation.width - image_requisition.width) / 2;
    }

    image_allocation.x = widget_allocation.x + MAX (0, x);
    image_allocation.y = widget_allocation.y + MAX (0, y);
    image_allocation.width = image_requisition.width;
    image_allocation.height = image_requisition.height;

    gtk_widget_size_allocate (image_menu_item->image, &image_allocation);
}

static void
mate_image_menu_item_add (GtkContainer *container,
                          GtkWidget    *widget)
{
    GTK_CONTAINER_CLASS (mate_image_menu_item_parent_class)->add (container, widget);
}

static void
mate_image_menu_item_forall (GtkContainer   *container,
                             gboolean        include_internals,
                             GtkCallback     callback,
                             gpointer        callback_data)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (container);

    GTK_CONTAINER_CLASS (mate_image_menu_item_parent_class)->forall (container,
                                                                     include_internals,
                                                                     callback,
                                                                     callback_data);

    if (include_internals && image_menu_item->image)
        (* callback) (image_menu_item->image, callback_data);
}

static void
mate_image_menu_item_remove (GtkContainer *container,
                             GtkWidget    *child)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (container);

    if (child == image_menu_item->image)
    {
        gboolean widget_was_visible;

        widget_was_visible = gtk_widget_get_visible (child);

        gtk_widget_unparent (child);
        image_menu_item->image = NULL;

        if (widget_was_visible &&
            gtk_widget_get_visible (GTK_WIDGET (container)))
            gtk_widget_queue_resize (GTK_WIDGET (container));

        g_object_notify (G_OBJECT (image_menu_item), "image");
    }
    else
    {
        GTK_CONTAINER_CLASS (mate_image_menu_item_parent_class)->remove (container, child);
    }
}

static void
mate_image_menu_item_toggle_size_request (GtkMenuItem *menu_item,
                                          gint        *requisition)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (menu_item);
    GtkPackDirection pack_dir;
    GtkWidget *parent;
    GtkWidget *widget = GTK_WIDGET (menu_item);

    parent = gtk_widget_get_parent (widget);

    if (GTK_IS_MENU_BAR (parent))
        pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
    else
        pack_dir = GTK_PACK_DIRECTION_LTR;

    *requisition = 0;

    if (image_menu_item->image && gtk_widget_get_visible (image_menu_item->image))
    {
        GtkRequisition image_requisition;
        guint toggle_spacing;

        gtk_widget_get_preferred_size (image_menu_item->image, &image_requisition, NULL);

        gtk_widget_style_get (GTK_WIDGET (menu_item),
                             "toggle-spacing", &toggle_spacing,
                              NULL);

        if (pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL)
        {
            if (image_requisition.width > 0)
              *requisition = image_requisition.width + toggle_spacing;
        }
        else
        {
            if (image_requisition.height > 0)
              *requisition = image_requisition.height + toggle_spacing;
        }
    }
}

static void
mate_image_menu_item_set_label (GtkMenuItem *menu_item,
                                const gchar *label)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (menu_item);

    if (image_menu_item->label != label)
    {
        g_free (image_menu_item->label);
        image_menu_item->label = g_strdup (label);
        GTK_MENU_ITEM_CLASS (mate_image_menu_item_parent_class)->set_label (menu_item, label);
        g_object_notify (G_OBJECT (menu_item), "label");
    }
}

static const gchar *
mate_image_menu_item_get_label (GtkMenuItem *menu_item)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (menu_item);

    return image_menu_item->label;
}

static void
mate_image_menu_item_finalize (GObject *object)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (object);

    g_free (image_menu_item->label);
    image_menu_item->label  = NULL;

    G_OBJECT_CLASS (mate_image_menu_item_parent_class)->finalize (object);
}

static void
mate_image_menu_item_set_property (GObject      *object,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (object);

    switch (prop_id)
    {
        case PROP_IMAGE:
            mate_image_menu_item_set_image (image_menu_item, (GtkWidget *) g_value_get_object (value));
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
mate_image_menu_item_get_property (GObject    *object,
                                   guint       prop_id,
                                   GValue     *value,
                                   GParamSpec *pspec)
{
    MateImageMenuItem *image_menu_item = MATE_IMAGE_MENU_ITEM (object);

    switch (prop_id)
    {
        case PROP_IMAGE:
            g_value_set_object (value, mate_image_menu_item_get_image (image_menu_item));
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
mate_image_menu_item_class_init (MateImageMenuItemClass *class)
{
    GObjectClass *gobject_class = (GObjectClass*) class;
    GtkWidgetClass *widget_class = (GtkWidgetClass*) class;
    GtkMenuItemClass *menu_item_class = (GtkMenuItemClass*) class;
    GtkContainerClass *container_class = (GtkContainerClass*) class;

    widget_class->destroy = mate_image_menu_item_destroy;
    widget_class->get_preferred_width = mate_image_menu_item_get_preferred_width;
    widget_class->get_preferred_height = mate_image_menu_item_get_preferred_height;
    widget_class->get_preferred_height_for_width = mate_image_menu_item_get_preferred_height_for_width;
    widget_class->size_allocate = mate_image_menu_item_size_allocate;

    container_class->add    = mate_image_menu_item_add;
    container_class->forall = mate_image_menu_item_forall;
    container_class->remove = mate_image_menu_item_remove;

    menu_item_class->toggle_size_request = mate_image_menu_item_toggle_size_request;
    menu_item_class->set_label           = mate_image_menu_item_set_label;
    menu_item_class->get_label           = mate_image_menu_item_get_label;

    gobject_class->finalize     = mate_image_menu_item_finalize;
    gobject_class->set_property = mate_image_menu_item_set_property;
    gobject_class->get_property = mate_image_menu_item_get_property;

  /**
   * MateImageMenuItem:image:
   *
   * Child widget to appear next to the menu text.
   *
   */
    g_object_class_install_property (gobject_class,
                                     PROP_IMAGE,
                                     g_param_spec_object ("image",
                                                          _("Image widget"),
                                                          _("Child widget to appear next to the menu text"),
                                                          GTK_TYPE_WIDGET,
                                                          G_PARAM_READWRITE | G_PARAM_DEPRECATED));
}

static void
mate_image_menu_item_init (MateImageMenuItem *image_menu_item)
{
    image_menu_item->image = NULL;
    image_menu_item->label  = NULL;
}

/**
 * mate_image_menu_item_new:
 *
 * Creates a new #MateImageMenuItem with an empty label.
 *
 * Returns: a new #MateImageMenuItem
 *
 */
GtkWidget*
mate_image_menu_item_new (void)
{
    return g_object_new (MATE_TYPE_IMAGE_MENU_ITEM, NULL);
}

/**
 * mate_image_menu_item_new_with_label:
 * @label: the text of the menu item.
 *
 * Creates a new #MateImageMenuItem containing a label.
 *
 * Returns: a new #MateImageMenuItem.
 *
 */
GtkWidget*
mate_image_menu_item_new_with_label (const gchar *label)
{
    return g_object_new (MATE_TYPE_IMAGE_MENU_ITEM,
                         "label", label,
                         NULL);
}

/**
 * mate_image_menu_item_new_with_mnemonic:
 * @label: the text of the menu item, with an underscore in front of the
 *         mnemonic character
 *
 * Creates a new #MateImageMenuItem containing a label. The label
 * will be created using gtk_label_new_with_mnemonic(), so underscores
 * in @label indicate the mnemonic for the menu item.
 *
 * Returns: a new #MateImageMenuItem
 *
 */
GtkWidget*
mate_image_menu_item_new_with_mnemonic (const gchar *label)
{
    return g_object_new (MATE_TYPE_IMAGE_MENU_ITEM,
                         "use-underline", TRUE,
                         "label", label,
                         NULL);
}

/**
 * mate_image_menu_item_set_image:
 * @image_menu_item: a #MateImageMenuItem.
 * @image: (allow-none): a widget to set as the image for the menu item.
 *
 * Sets the image of @image_menu_item to the given widget.
 * Note that it depends on the show-menu-images setting whether
 * the image will be displayed or not.
 *
 */
void
mate_image_menu_item_set_image (MateImageMenuItem *image_menu_item,
                                GtkWidget         *image)
{
    g_return_if_fail (MATE_IS_IMAGE_MENU_ITEM (image_menu_item));

    if (image == image_menu_item->image)
        return;

    if (image_menu_item->image)
        gtk_container_remove (GTK_CONTAINER (image_menu_item),
                              image_menu_item->image);

    image_menu_item->image = image;

    if (image == NULL)
        return;

    gtk_widget_set_parent (image, GTK_WIDGET (image_menu_item));
    g_object_set (image,
                  "visible", TRUE,
                  "no-show-all", TRUE,
                  NULL);
    gtk_image_set_pixel_size (GTK_IMAGE (image), 16);

    g_object_notify (G_OBJECT (image_menu_item), "image");
}

/**
 * mate_image_menu_item_get_image:
 * @image_menu_item: a #MateImageMenuItem
 *
 * Gets the widget that is currently set as the image of @image_menu_item.
 * See mate_image_menu_item_set_image().
 *
 * Returns: (transfer none): the widget set as image of @image_menu_item
 *
 **/
GtkWidget*
mate_image_menu_item_get_image (MateImageMenuItem *image_menu_item)
{
    g_return_val_if_fail (MATE_IS_IMAGE_MENU_ITEM (image_menu_item), NULL);

    return image_menu_item->image;
}
