/*
 * * Copyright (C) 2014 Eric Koegel <eric@xfce.org>
 *
 * Licensed under the GNU General Public License Version 2
 *
 * 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
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>
#include <libxfce4util/libxfce4util.h>
#include <libxfce4ui/libxfce4ui.h>
#include <dbus/dbus-glib.h>
#include <upower.h>
#include <xfconf/xfconf.h>

#include "common/xfpm-common.h"
#include "common/xfpm-config.h"
#include "common/xfpm-icons.h"
#include "common/xfpm-power-common.h"
#include "common/xfpm-brightness.h"

#include "power-manager-button.h"
#include "scalemenuitem.h"


#define SET_LEVEL_TIMEOUT (50)
#define SAFE_SLIDER_MIN_LEVEL (5)

#define POWER_MANAGER_BUTTON_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), POWER_MANAGER_TYPE_BUTTON, PowerManagerButtonPrivate))

struct PowerManagerButtonPrivate
{
#ifdef XFCE_PLUGIN
    XfcePanelPlugin *plugin;
#endif

    XfconfChannel   *channel;

    UpClient        *upower;

    /* A list of BatteryDevices  */
    GList           *devices;

    /* The left-click popup menu, if one is being displayed */
    GtkWidget       *menu;

    /* The actual panel icon image */
    GtkWidget       *panel_icon_image;
    /* Keep track of icon name to redisplay during size changes */
    gchar           *panel_icon_name;
    /* Keep track of the last icon size for use during updates */
    gint             panel_icon_width;

    /* Upower 0.99 has a display device that can be used for the
     * panel image and tooltip description */
    UpDevice        *display_device;

    XfpmBrightness  *brightness;

    /* display brightness slider widget */
    GtkWidget       *range;
    /* Some laptops (and mostly newer ones with intel graphics) can turn off the
     * backlight completely. If the user is not careful and sets the brightness
     * very low using the slider, he might not be able to see the screen contents
     * anymore. Brightness keys do not work on every laptop, so it's better to use
     * a safe default minimum level that the user can change via the settings
     * editor if desired.
     */
    gint32           brightness_min_level;

    /* filter range value changed events for snappier UI feedback */
    guint            set_level_timeout;
};

typedef struct
{
    GdkPixbuf   *pix;               /* Icon */
    GtkWidget   *img;               /* Icon image in the menu */
    gchar       *details;           /* Description of the device + state */
    gchar       *object_path;       /* UpDevice object path */
    UpDevice    *device;            /* Pointer to the UpDevice */
    gulong       changed_signal_id; /* device changed callback id */
    gulong       expose_signal_id;  /* expose-event callback id */
    GtkWidget   *menu_item;         /* The device's item on the menu (if shown) */
} BatteryDevice;

typedef enum
{
    PROP_0 = 0,
    PROP_BRIGHTNESS_MIN_LEVEL,
} POWER_MANAGER_BUTTON_PROPERTIES;


G_DEFINE_TYPE (PowerManagerButton, power_manager_button, GTK_TYPE_TOGGLE_BUTTON)

static void power_manager_button_finalize   (GObject *object);
static GList* find_device_in_list (PowerManagerButton *button, const gchar *object_path);
static gboolean power_manager_button_device_icon_expose (GtkWidget *img, GdkEventExpose *event, gpointer userdata);
static gboolean power_manager_button_set_icon (PowerManagerButton *button);
static gboolean power_manager_button_press_event (GtkWidget *widget, GdkEventButton *event);
static void power_manager_button_show_menu (PowerManagerButton *button);
static gboolean power_manager_button_menu_add_device (PowerManagerButton *button, BatteryDevice *battery_device, gboolean append);
static void increase_brightness (PowerManagerButton *button);
static void decrease_brightness (PowerManagerButton *button);
static void battery_device_remove_pix (BatteryDevice *battery_device);


static BatteryDevice*
get_display_device (PowerManagerButton *button)
{
    GList *item = NULL;
    gdouble highest_percentage = 0;
    BatteryDevice *display_device = NULL;

    TRACE("entering");

    g_return_val_if_fail ( POWER_MANAGER_IS_BUTTON(button), NULL );

    if (button->priv->display_device)
    {
        item = find_device_in_list (button, up_device_get_object_path (button->priv->display_device));
        if (item)
        {
            return item->data;
        }
    }

    /* We want to find the battery or ups device with the highest percentage
     * and use that to get our tooltip from */
    for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
    {
        BatteryDevice *battery_device = item->data;
        guint type = 0;
        gdouble percentage;

        if (!battery_device->device || !UP_IS_DEVICE(battery_device->device))
        {
            continue;
        }

        g_object_get (battery_device->device,
                      "kind", &type,
                      "percentage", &percentage,
                      NULL);

        if ( type == UP_DEVICE_KIND_BATTERY || type == UP_DEVICE_KIND_UPS)
        {
            if ( highest_percentage < percentage )
            {
                display_device = battery_device;
                highest_percentage = percentage;
            }
        }
    }

    return display_device;
}

static void
power_manager_button_set_tooltip (PowerManagerButton *button)
{
    BatteryDevice *display_device = get_display_device (button);

    TRACE("entering");

    if (!GTK_IS_WIDGET (button))
    {
        g_critical ("power_manager_button_set_tooltip: !GTK_IS_WIDGET (button)");
        return;
    }

    if ( display_device )
    {
        /* if we have something, display it */
        if( display_device->details )
        {
            gtk_widget_set_tooltip_markup (GTK_WIDGET (button), display_device->details);
            return;
        }
    }

    /* Odds are this is a desktop without any batteries attached */
    gtk_widget_set_tooltip_text (GTK_WIDGET (button), _("Display battery levels for attached devices"));
}

static GList*
find_device_in_list (PowerManagerButton *button, const gchar *object_path)
{
    GList *item = NULL;

    TRACE("entering");

    g_return_val_if_fail ( POWER_MANAGER_IS_BUTTON(button), NULL );

    for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
    {
        BatteryDevice *battery_device = item->data;
        if (battery_device == NULL)
        {
            DBG("!battery_device");
            continue;
        }

        if (g_strcmp0 (battery_device->object_path, object_path) == 0)
            return item;
    }

    return NULL;
}

static gboolean
power_manager_button_device_icon_expose (GtkWidget *img, GdkEventExpose *event, gpointer userdata)
{
    cairo_t *cr;
    UpDevice *device = NULL;
    guint type = 0, state = 0;
    gdouble percentage;
    gint height, width;
    gdouble min_height = 2;
    PangoLayout *layout = NULL;
    PangoRectangle ink_extent, log_extent;

    TRACE("entering");

    /* sanity checks */
    if (!img || !GTK_IS_WIDGET (img))
        return FALSE;

    if (UP_IS_DEVICE (userdata))
    {
        device = UP_DEVICE(userdata);

        g_object_get (device,
                      "kind", &type,
                      "state", &state,
                      "percentage", &percentage,
                      NULL);

        /* Don't draw the progressbar for Battery and UPS */
        if (type == UP_DEVICE_KIND_BATTERY || type == UP_DEVICE_KIND_UPS)
            return FALSE;
    }
    else
    {
        /* If the UpDevice hasn't fully updated yet it then we'll want
         * a question mark for sure. */
        state = UP_DEVICE_STATE_UNKNOWN;
    }

    cr = gdk_cairo_create (img->window);
    width = img->allocation.width;
    height = img->allocation.height;

    if (state != UP_DEVICE_STATE_UNKNOWN)
    {
        /* Draw the trough of the progressbar */
        cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
        cairo_set_line_width (cr, 1.0);
        cairo_rectangle (cr, width - 3.5, img->allocation.y + 1.5, 5, height - 2);
        cairo_set_source_rgb (cr, 0.87, 0.87, 0.87);
        cairo_fill_preserve (cr);
        cairo_set_source_rgb (cr, 0.53, 0.54, 0.52);
        cairo_stroke (cr);

        /* Draw the fill of the progressbar
           Use yellow for 20% and below, green for 100%, red for 5% and below and blue for the rest */
        cairo_set_operator (cr, CAIRO_OPERATOR_OVER);

        if ((height * (percentage / 100)) > min_height)
           min_height = (height - 3) * (percentage / 100);

        cairo_rectangle (cr, width - 3, img->allocation.y + height - min_height - 1, 4, min_height);
        if (percentage > 5 && percentage < 20)
            cairo_set_source_rgb (cr, 0.93, 0.83, 0.0);
        else if (percentage > 20 && percentage < 100)
            cairo_set_source_rgb (cr, 0.2, 0.4, 0.64);
        else if (percentage == 100)
            cairo_set_source_rgb (cr, 0.45, 0.82, 0.08);
        else
            cairo_set_source_rgb (cr, 0.94, 0.16, 0.16);
        cairo_fill (cr);

        cairo_rectangle (cr, width - 2.5, img->allocation.y + 2.5, 3, height - 4);
        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.75);
        cairo_stroke (cr);
    }
    else
    {
        /* Draw a bubble with a question mark for devices with unknown state */
        cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
        cairo_set_line_width (cr, 1.0);
        cairo_arc(cr, width - 4.5, img->allocation.y + 6.5, 6, 0, 2*3.14159);
        cairo_set_source_rgb (cr, 0.2, 0.54, 0.9);
        cairo_fill_preserve (cr);
        cairo_set_source_rgb (cr, 0.1, 0.37, 0.6);
        cairo_stroke (cr);

        layout = gtk_widget_create_pango_layout (GTK_WIDGET (img), "?");
        pango_layout_set_font_description (layout, pango_font_description_from_string ("Sans Bold 9"));
        pango_layout_get_pixel_extents (layout, &ink_extent, &log_extent);
        cairo_move_to (cr, (width - 5.5) - (log_extent.width / 2), (img->allocation.y + 5.5) - (log_extent.height / 2));
        cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
        pango_cairo_show_layout (cr, layout);
    }

    cairo_destroy (cr);
    if (layout)
        g_object_unref (layout);
    return FALSE;
}


static void
power_manager_button_update_device_icon_and_details (PowerManagerButton *button, UpDevice *device)
{
    GList *item;
    BatteryDevice *battery_device, *display_device;
    const gchar *object_path = up_device_get_object_path(device);
    gchar *details, *icon_name;
    GdkPixbuf *pix;
    guint type = 0;

    TRACE("entering for %s", object_path);

    g_return_if_fail ( POWER_MANAGER_IS_BUTTON (button) );

    item = find_device_in_list (button, object_path);

    if (item == NULL)
	return;

    battery_device = item->data;

    /* hack, this depends on XFPM_DEVICE_TYPE_* being in sync with UP_DEVICE_KIND_* */
    g_object_get (device,
		  "kind", &type,
		   NULL);

    icon_name = get_device_icon_name (button->priv->upower, device);
    details = get_device_description(button->priv->upower, device);

    pix = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
				    icon_name,
				    32,
				    GTK_ICON_LOOKUP_USE_BUILTIN,
				    NULL);

    if (battery_device->details)
	g_free(battery_device->details);
    battery_device->details = details;

    /* If we had an image before, remove it and the callback */
    battery_device_remove_pix(battery_device);

    battery_device->pix = pix;

    /* Get the display device, which may now be this one */
    display_device = get_display_device (button);
    if ( battery_device == display_device)
    {
	DBG("this is the display device, updating");
	/* it is! update the panel button */
	g_free(button->priv->panel_icon_name);
	button->priv->panel_icon_name = icon_name;
	power_manager_button_set_icon (button);
	/* update tooltip */
	power_manager_button_set_tooltip (button);
    }

    /* If the menu is being displayed, update it */
    if (button->priv->menu && battery_device->menu_item)
    {
        gtk_menu_item_set_label (GTK_MENU_ITEM (battery_device->menu_item), details);

        /* update the image, keep track of the signal ids and the img
         * so we can disconnect it later */
        battery_device->img = gtk_image_new_from_pixbuf(battery_device->pix);
        g_object_ref (battery_device->img);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(battery_device->menu_item), battery_device->img);
        battery_device->expose_signal_id = g_signal_connect_after (G_OBJECT (battery_device->img),
                                                                   "expose-event",
                                                                   G_CALLBACK (power_manager_button_device_icon_expose),
                                                                   device);
    }
}

static void
#if UP_CHECK_VERSION(0, 99, 0)
device_changed_cb (UpDevice *device, GParamSpec *pspec, PowerManagerButton *button)
#else
device_changed_cb (UpDevice *device, PowerManagerButton *button)
#endif
{
    power_manager_button_update_device_icon_and_details (button, device);
}

static void
power_manager_button_add_device (UpDevice *device, PowerManagerButton *button)
{
    BatteryDevice *battery_device;
    guint type = 0;
    const gchar *object_path = up_device_get_object_path(device);
    gulong signal_id;

    TRACE("entering for %s", object_path);

    g_return_if_fail ( POWER_MANAGER_IS_BUTTON (button ) );

    /* don't add the same device twice */
    if ( find_device_in_list (button, object_path) )
	return;

    battery_device = g_new0 (BatteryDevice, 1);

    /* hack, this depends on XFPM_DEVICE_TYPE_* being in sync with UP_DEVICE_KIND_* */
    g_object_get (device,
		  "kind", &type,
		   NULL);


#if UP_CHECK_VERSION(0, 99, 0)
    signal_id = g_signal_connect (device, "notify", G_CALLBACK (device_changed_cb), button);
#else
    signal_id = g_signal_connect (device, "changed", G_CALLBACK (device_changed_cb), button);
#endif

    /* populate the struct */
    battery_device->object_path = g_strdup (object_path);
    battery_device->changed_signal_id = signal_id;
    battery_device->device = g_object_ref(device);

    /* add it to the list */
    button->priv->devices = g_list_append (button->priv->devices, battery_device);

    /* Add the icon and description for the device */
    power_manager_button_update_device_icon_and_details (button, device);

    /* If the menu is being shown, add this new device to it */
    if (button->priv->menu)
    {
	power_manager_button_menu_add_device (button, battery_device, FALSE);
    }
}

/* This function unrefs the pix and img from the battery device and
 * disconnects the expose-event callback on the img.
 */
static void
battery_device_remove_pix (BatteryDevice *battery_device)
{
    TRACE("entering");

    if (battery_device == NULL)
        return;

    if (G_IS_OBJECT(battery_device->pix))
    {
        if (GTK_IS_WIDGET(battery_device->img))
        {
            if (battery_device->expose_signal_id != 0)
            {
                g_signal_handler_disconnect (battery_device->img, battery_device->expose_signal_id);
                battery_device->expose_signal_id = 0;
            }
            g_object_unref (battery_device->img);
            battery_device->img = NULL;
        }
        g_object_unref (battery_device->pix);
        battery_device->pix = NULL;
    }
}

static void
power_manager_button_remove_device (PowerManagerButton *button, const gchar *object_path)
{
    GList *item;
    BatteryDevice *battery_device;

    TRACE("entering for %s", object_path);

    item = find_device_in_list (button, object_path);

    if (item == NULL)
	return;

    battery_device = item->data;

    /* If it is being shown in the menu, remove it */
    if(battery_device->menu_item && button->priv->menu)
        gtk_container_remove(GTK_CONTAINER(button->priv->menu), battery_device->menu_item);

    g_free(battery_device->details);
    g_free(battery_device->object_path);

    if (battery_device->device != NULL && UP_IS_DEVICE(battery_device->device))
    {
        /* disconnect the signal handler if we were using it */
        if (battery_device->changed_signal_id != 0)
            g_signal_handler_disconnect (battery_device->device, battery_device->changed_signal_id);
        battery_device->changed_signal_id = 0;

        g_object_unref (battery_device->device);
        battery_device->device = NULL;
    }

    /* remove it item and free the battery device */
    button->priv->devices = g_list_delete_link (button->priv->devices, item);
}

static void
device_added_cb (UpClient *upower, UpDevice *device, PowerManagerButton *button)
{
    power_manager_button_add_device (device, button);
}

#if UP_CHECK_VERSION(0, 99, 0)
static void
device_removed_cb (UpClient *upower, const gchar *object_path, PowerManagerButton *button)
{
    power_manager_button_remove_device (button, object_path);
}
#else
static void
device_removed_cb (UpClient *upower, UpDevice *device, PowerManagerButton *button)
{
    const gchar *object_path = up_device_get_object_path(device);
    power_manager_button_remove_device (button, object_path);
}
#endif

static void
power_manager_button_add_all_devices (PowerManagerButton *button)
{
#if !UP_CHECK_VERSION(0, 99, 0)
    /* the device-add callback is called for each device */
    up_client_enumerate_devices_sync(button->priv->upower, NULL, NULL);
#else
    GPtrArray *array = NULL;
    guint i;

    button->priv->display_device = up_client_get_display_device (button->priv->upower);
    power_manager_button_add_device (button->priv->display_device, button);

    array = up_client_get_devices(button->priv->upower);

    if ( array )
    {
	for ( i = 0; i < array->len; i++)
	{
	    UpDevice *device = g_ptr_array_index (array, i);

	    power_manager_button_add_device (device, button);
	}
	g_ptr_array_free (array, TRUE);
    }
#endif
}

static void
brightness_up (PowerManagerButton *button)
{
    gint32 level;
    gint32 max_level;

    xfpm_brightness_get_level (button->priv->brightness, &level);
    max_level = xfpm_brightness_get_max_level (button->priv->brightness);

    if ( level < max_level )
    {
        increase_brightness (button);
    }
}

static void
brightness_down (PowerManagerButton *button)
{
    gint32 level;
    xfpm_brightness_get_level (button->priv->brightness, &level);

    if ( level > button->priv->brightness_min_level )
    {
        decrease_brightness (button);
    }
}

static gboolean
power_manager_button_scroll_event (GtkWidget *widget, GdkEventScroll *ev)
{
    gboolean hw_found;
    PowerManagerButton *button;

    button = POWER_MANAGER_BUTTON (widget);

    hw_found = xfpm_brightness_has_hw (button->priv->brightness);

    if ( !hw_found )
        return FALSE;

    if ( ev->direction == GDK_SCROLL_UP )
    {
        brightness_up (button);
        return TRUE;
    }
    else if ( ev->direction == GDK_SCROLL_DOWN )
    {
        brightness_down (button);
        return TRUE;
    }
    return FALSE;
}

static void
set_brightness_min_level(PowerManagerButton *button, gint32 new_brightness_level)
{
    gint32 max_level = xfpm_brightness_get_max_level (button->priv->brightness);

    /* sanity check */
    if (new_brightness_level > max_level)
        new_brightness_level = -1;

    /* -1 = auto, we set the step value to a hopefully sane default */
    if (new_brightness_level == -1)
    {
        button->priv->brightness_min_level = (max_level > 100) ? SAFE_SLIDER_MIN_LEVEL : 0;
    }
    else
    {
        button->priv->brightness_min_level = new_brightness_level;
    }

    DBG("button->priv->brightness_min_level : %d", button->priv->brightness_min_level);

    /* update the range if it's being shown */
    if (button->priv->range)
    {
        gtk_range_set_range (GTK_RANGE(button->priv->range), button->priv->brightness_min_level, max_level);
    }
}

static void
power_manager_button_set_property(GObject *object,
                                  guint property_id,
                                  const GValue *value,
                                  GParamSpec *pspec)
{
    PowerManagerButton *button;

    button = POWER_MANAGER_BUTTON (object);

    switch(property_id)
    {
        case PROP_BRIGHTNESS_MIN_LEVEL:
            set_brightness_min_level (button, g_value_get_int(value));
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            break;
    }
}

static void
power_manager_button_get_property(GObject *object,
                                  guint property_id,
                                  GValue *value,
                                  GParamSpec *pspec)
{
    PowerManagerButton *button;

    button = POWER_MANAGER_BUTTON (object);

    switch(property_id)
    {
        case PROP_BRIGHTNESS_MIN_LEVEL:
            g_value_set_int(value, button->priv->brightness_min_level);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            break;
    }
}

static void
power_manager_button_class_init (PowerManagerButtonClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

    object_class->finalize = power_manager_button_finalize;
    object_class->set_property = power_manager_button_set_property;
    object_class->get_property = power_manager_button_get_property;

    widget_class->button_press_event = power_manager_button_press_event;
    widget_class->scroll_event = power_manager_button_scroll_event;

    g_type_class_add_private (klass, sizeof (PowerManagerButtonPrivate));

#define XFPM_PARAM_FLAGS  (G_PARAM_READWRITE \
                           | G_PARAM_CONSTRUCT \
                           | G_PARAM_STATIC_NAME \
                           | G_PARAM_STATIC_NICK \
                           | G_PARAM_STATIC_BLURB)

    /* We allow and default to -1 only so that we can automagically set a
     * sane value if the user hasn't selected one already */
    g_object_class_install_property(object_class, PROP_BRIGHTNESS_MIN_LEVEL,
                                    g_param_spec_int(BRIGHTNESS_SLIDER_MIN_LEVEL,
                                                     BRIGHTNESS_SLIDER_MIN_LEVEL,
                                                     BRIGHTNESS_SLIDER_MIN_LEVEL,
                                                     -1, G_MAXINT32, -1,
                                                     XFPM_PARAM_FLAGS));
#undef XFPM_PARAM_FLAGS
}

static void
power_manager_button_init (PowerManagerButton *button)
{
    GError *error = NULL;

    button->priv = POWER_MANAGER_BUTTON_GET_PRIVATE (button);

    gtk_widget_set_can_default (GTK_WIDGET (button), FALSE);
    gtk_widget_set_can_focus (GTK_WIDGET (button), FALSE);
    gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
    gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);

    button->priv->brightness = xfpm_brightness_new ();
    xfpm_brightness_setup (button->priv->brightness);
    button->priv->set_level_timeout = 0;

    button->priv->upower  = up_client_new ();
    if ( !xfconf_init (&error) )
    {
        g_critical ("xfconf_init failed: %s\n", error->message);
        g_error_free (error);
    }
    else
    {
        button->priv->channel = xfconf_channel_get ("xfce4-power-manager");
    }

    /* Sane defaults for the panel icon */
    button->priv->panel_icon_name = g_strdup(XFPM_AC_ADAPTER_ICON);
    button->priv->panel_icon_width = 24;

    g_signal_connect (button->priv->upower, "device-added", G_CALLBACK (device_added_cb), button);
    g_signal_connect (button->priv->upower, "device-removed", G_CALLBACK (device_removed_cb), button);
}

static void
power_manager_button_finalize (GObject *object)
{
    PowerManagerButton *button;

    button = POWER_MANAGER_BUTTON (object);

    g_free(button->priv->panel_icon_name);

    if (button->priv->set_level_timeout)
    {
        g_source_remove(button->priv->set_level_timeout);
        button->priv->set_level_timeout = 0;
    }

    g_signal_handlers_disconnect_by_data (button->priv->upower, button);

#ifdef XFCE_PLUGIN
    g_object_unref (button->priv->plugin);
#endif

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

GtkWidget *
#ifdef XFCE_PLUGIN
power_manager_button_new (XfcePanelPlugin *plugin)
#endif
#ifdef LXDE_PLUGIN
power_manager_button_new (void)
#endif
{
    PowerManagerButton *button = NULL;
    button = g_object_new (POWER_MANAGER_TYPE_BUTTON, NULL, NULL);

#ifdef XFCE_PLUGIN
    button->priv->plugin = XFCE_PANEL_PLUGIN (g_object_ref (plugin));
#endif

    xfconf_g_property_bind(button->priv->channel,
                           PROPERTIES_PREFIX BRIGHTNESS_SLIDER_MIN_LEVEL, G_TYPE_INT,
                           G_OBJECT(button), BRIGHTNESS_SLIDER_MIN_LEVEL);

    return GTK_WIDGET (button);
}

static gboolean
power_manager_button_set_icon (PowerManagerButton *button)
{
    GdkPixbuf *pixbuf;

    DBG("icon_width %d", button->priv->panel_icon_width);

    pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
                                       button->priv->panel_icon_name,
                                       button->priv->panel_icon_width,
                                       GTK_ICON_LOOKUP_GENERIC_FALLBACK,
                                       NULL);

    if ( pixbuf )
    {
        gtk_image_set_from_pixbuf (GTK_IMAGE (button->priv->panel_icon_image), pixbuf);
        g_object_unref (pixbuf);
        return TRUE;
    }

    return FALSE;
}

void
power_manager_button_set_width (PowerManagerButton *button, gint width)
{
    g_return_if_fail (POWER_MANAGER_IS_BUTTON (button));

    button->priv->panel_icon_width = width;

    power_manager_button_set_icon (button);
}

static gboolean
power_manager_button_press_event (GtkWidget *widget, GdkEventButton *event)
{
    PowerManagerButton *button = POWER_MANAGER_BUTTON (widget);

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
    power_manager_button_show_menu (button);
    return TRUE;
}

#ifdef XFCE_PLUGIN
static gboolean
power_manager_button_size_changed_cb (XfcePanelPlugin *plugin, gint size, PowerManagerButton *button)
{
    gint width;
    gint xthickness;
    gint ythickness;

    g_return_val_if_fail (POWER_MANAGER_IS_BUTTON (button), FALSE);
    g_return_val_if_fail (XFCE_IS_PANEL_PLUGIN (plugin), FALSE);

    xthickness = gtk_widget_get_style(GTK_WIDGET(button))->xthickness;
    ythickness = gtk_widget_get_style(GTK_WIDGET(button))->ythickness;
    size /= xfce_panel_plugin_get_nrows (plugin);
    width = size - 2* MAX (xthickness, ythickness);

    gtk_widget_set_size_request (GTK_WIDGET(plugin), size + xthickness, size + ythickness);
    button->priv->panel_icon_width = width;

    return power_manager_button_set_icon (button);
}

static void
power_manager_button_style_set_cb (XfcePanelPlugin *plugin, GtkStyle *prev_style, PowerManagerButton *button)
{
    gtk_widget_reset_rc_styles (GTK_WIDGET (plugin));
    power_manager_button_size_changed_cb (plugin, xfce_panel_plugin_get_size (plugin), button);
}

static void
power_manager_button_free_data_cb (XfcePanelPlugin *plugin, PowerManagerButton *button)
{
    gtk_widget_destroy (GTK_WIDGET (button));
}
#endif

static void
help_cb (GtkMenuItem *menuitem, gpointer user_data)
{
#if LIBXFCE4UI_CHECK_VERSION(4, 11, 1)
    xfce_dialog_show_help_with_version (NULL, "xfce4-power-manager", "start", NULL, XFPM_VERSION_SHORT);
#else
    xfce_dialog_show_help (NULL, "xfce4-power-manager", "start", NULL);
#endif
}

void
power_manager_button_show (PowerManagerButton *button)
{
    GtkWidget *mi;

    g_return_if_fail (POWER_MANAGER_IS_BUTTON (button));

#ifdef XFCE_PLUGIN
    xfce_panel_plugin_add_action_widget (button->priv->plugin, GTK_WIDGET (button));
    xfce_panel_plugin_set_small (button->priv->plugin, TRUE);
#endif

    button->priv->panel_icon_image = gtk_image_new ();
    gtk_container_add (GTK_CONTAINER (button), button->priv->panel_icon_image);

    /* help dialog */
    mi = gtk_image_menu_item_new_from_stock (GTK_STOCK_HELP, NULL);
    gtk_widget_set_sensitive (mi, TRUE);
    gtk_widget_show (mi);
    g_signal_connect (mi, "activate", G_CALLBACK (help_cb), button);

#ifdef XFCE_PLUGIN
    xfce_panel_plugin_menu_insert_item (button->priv->plugin, GTK_MENU_ITEM (mi));

    g_signal_connect (button->priv->plugin, "size-changed",
                      G_CALLBACK (power_manager_button_size_changed_cb), button);

    g_signal_connect (button->priv->plugin, "style-set",
                      G_CALLBACK (power_manager_button_style_set_cb), button);

    g_signal_connect (button->priv->plugin, "free-data",
                      G_CALLBACK (power_manager_button_free_data_cb), button);
#endif

    gtk_widget_show_all (GTK_WIDGET(button));
    power_manager_button_set_tooltip (button);

    /* Add all the devcies currently attached to the system */
    power_manager_button_add_all_devices (button);
}

static void
menu_destroyed_cb(GtkMenuShell *menu, gpointer user_data)
{
    PowerManagerButton *button = POWER_MANAGER_BUTTON (user_data);

    TRACE("entering");

    /* menu destroyed, range slider is gone */
    button->priv->range = NULL;

    /* untoggle panel icon */
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);

    button->priv->menu = NULL;
}

static void
menu_item_destroyed_cb(GtkWidget *object, gpointer user_data)
{
    PowerManagerButton *button = POWER_MANAGER_BUTTON (user_data);
    GList *item;

    for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
    {
        BatteryDevice *battery_device = item->data;

        if (battery_device->menu_item == object)
        {
            battery_device->menu_item = NULL;
            return;
        }
    }
}

static void
menu_item_activate_cb(GtkWidget *object, gpointer user_data)
{
    PowerManagerButton *button = POWER_MANAGER_BUTTON (user_data);
    GList *item;

    for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
    {
        BatteryDevice *battery_device = item->data;

        if (battery_device->menu_item == object)
        {
            /* Call xfpm settings with the device id */
            xfpm_preferences_device_id (battery_device->object_path);
            return;
        }
    }
}

static gboolean
power_manager_button_menu_add_device (PowerManagerButton *button, BatteryDevice *battery_device, gboolean append)
{
    GtkWidget *mi, *label;
    guint type = 0;

    g_return_val_if_fail (POWER_MANAGER_IS_BUTTON (button), FALSE);

    /* We need a menu to attach it to */
    g_return_val_if_fail (button->priv->menu, FALSE);

    if (UP_IS_DEVICE(battery_device->device))
    {
        g_object_get (battery_device->device,
                      "kind", &type,
                      NULL);

        /* Don't add the display device or line power to the menu */
        if (type == UP_DEVICE_KIND_LINE_POWER || battery_device->device == button->priv->display_device)
        {
            DBG("filtering device from menu (display or line power device)");
            return FALSE;
        }
    }

    mi = gtk_image_menu_item_new_with_label(battery_device->details);
    /* Make the menu item be bold and multi-line */
    label = gtk_bin_get_child(GTK_BIN(mi));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);

    /* add the image */
    battery_device->img = gtk_image_new_from_pixbuf(battery_device->pix);
    g_object_ref (battery_device->img);
    gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), battery_device->img);

    /* keep track of the menu item in the battery_device so we can update it */
    battery_device->menu_item = mi;
    g_signal_connect(G_OBJECT(mi), "destroy", G_CALLBACK(menu_item_destroyed_cb), button);
    battery_device->expose_signal_id = g_signal_connect_after (G_OBJECT (battery_device->img),
                                                               "expose-event",
                                                               G_CALLBACK (power_manager_button_device_icon_expose),
                                                               battery_device->device);

    /* Active calls xfpm settings with the device's id to display details */
    g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(menu_item_activate_cb), button);

    /* Add it to the menu */
    gtk_widget_show(mi);
    if (append)
	gtk_menu_shell_append(GTK_MENU_SHELL(button->priv->menu), mi);
    else
	gtk_menu_shell_prepend(GTK_MENU_SHELL(button->priv->menu), mi);

    return TRUE;
}

static void
decrease_brightness (PowerManagerButton *button)
{
    gint32 level;

    TRACE("entering");

    if ( !xfpm_brightness_has_hw (button->priv->brightness) )
        return;

    xfpm_brightness_get_level (button->priv->brightness, &level);

    if ( level > button->priv->brightness_min_level )
    {
        xfpm_brightness_down (button->priv->brightness, &level);
        if (button->priv->range)
            gtk_range_set_value (GTK_RANGE (button->priv->range), level);
    }
}

static void
increase_brightness (PowerManagerButton *button)
{
    gint32 level, max_level;

    TRACE("entering");

    if ( !xfpm_brightness_has_hw (button->priv->brightness) )
        return;

    max_level = xfpm_brightness_get_max_level (button->priv->brightness);
    xfpm_brightness_get_level (button->priv->brightness, &level);

    if ( level < max_level )
    {
        xfpm_brightness_up (button->priv->brightness, &level);
        if (button->priv->range)
            gtk_range_set_value (GTK_RANGE (button->priv->range), level);
    }
}

static gboolean
brightness_set_level_with_timeout (PowerManagerButton *button)
{
    gint32 range_level, hw_level;

    TRACE("entering");

    range_level = (gint32) gtk_range_get_value (GTK_RANGE (button->priv->range));

    xfpm_brightness_get_level (button->priv->brightness, &hw_level);

    if ( hw_level != range_level )
    {
        xfpm_brightness_set_level (button->priv->brightness, range_level);
    }

    if (button->priv->set_level_timeout)
    {
        g_source_remove(button->priv->set_level_timeout);
        button->priv->set_level_timeout = 0;
    }

    return FALSE;
}

static void
range_value_changed_cb (PowerManagerButton *button, GtkWidget *widget)
{
    TRACE("entering");

    if (button->priv->set_level_timeout)
        return;

    button->priv->set_level_timeout =
        g_timeout_add(SET_LEVEL_TIMEOUT,
                      (GSourceFunc) brightness_set_level_with_timeout, button);
}

static void
range_scroll_cb (GtkWidget *widget, GdkEvent *event, PowerManagerButton *button)
{
    GdkEventScroll *scroll_event;

    TRACE("entering");

    scroll_event = (GdkEventScroll*)event;

    if (scroll_event->direction == GDK_SCROLL_UP)
        increase_brightness (button);
    else if (scroll_event->direction == GDK_SCROLL_DOWN)
        decrease_brightness (button);
}

static void
range_show_cb (GtkWidget *widget, PowerManagerButton *button)
{
    TRACE("entering");
    /* Release these grabs they will cause a lockup if pkexec is called
     * for the brightness helper */
    gdk_pointer_ungrab(GDK_CURRENT_TIME);
    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
    gtk_grab_remove(widget);
}

static void
power_manager_button_show_menu (PowerManagerButton *button)
{
    GtkWidget *menu, *mi, *img = NULL;
    GdkScreen *gscreen;
    GList *item;
    gboolean show_separator_flag = FALSE;
    gint32 max_level, current_level = 0;

    TRACE("entering");

    g_return_if_fail (POWER_MANAGER_IS_BUTTON (button));

    if(gtk_widget_has_screen(GTK_WIDGET(button)))
        gscreen = gtk_widget_get_screen(GTK_WIDGET(button));
    else
        gscreen = gdk_display_get_default_screen(gdk_display_get_default());

    menu = gtk_menu_new ();
    gtk_menu_set_screen(GTK_MENU(menu), gscreen);
    /* keep track of the menu while it's being displayed */
    button->priv->menu = menu;
    g_signal_connect(GTK_MENU_SHELL(menu), "deactivate", G_CALLBACK(menu_destroyed_cb), button);

    for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
    {
        BatteryDevice *battery_device = item->data;

        if (power_manager_button_menu_add_device (button, battery_device, TRUE))
        {
            /* If we add an item to the menu, show the separator */
            show_separator_flag = TRUE;
        }
    }

    if (show_separator_flag)
    {
        /* separator */
        mi = gtk_separator_menu_item_new();
        gtk_widget_show(mi);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
    }

    /* Display brightness slider - show if there's hardware support for it */
    if ( xfpm_brightness_has_hw (button->priv->brightness) )
    {
        GdkPixbuf *pix;

        max_level = xfpm_brightness_get_max_level (button->priv->brightness);

        mi = scale_menu_item_new_with_range (button->priv->brightness_min_level, max_level, 1);

        /* attempt to load and display the brightness icon */
        pix = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
                                       XFPM_DISPLAY_BRIGHTNESS_ICON,
                                       32,
                                       GTK_ICON_LOOKUP_GENERIC_FALLBACK,
                                       NULL);
        if (pix)
        {
            img = gtk_image_new_from_pixbuf (pix);
            gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM(mi), img);
        }

        scale_menu_item_set_description_label (SCALE_MENU_ITEM(mi), _("<b>Display brightness</b>"));

        /* range slider */
        button->priv->range = scale_menu_item_get_scale (SCALE_MENU_ITEM (mi));

        /* update the slider to the current brightness level */
        xfpm_brightness_get_level (button->priv->brightness, &current_level);
        gtk_range_set_value (GTK_RANGE(button->priv->range), current_level);

        g_signal_connect_swapped (mi, "value-changed", G_CALLBACK (range_value_changed_cb), button);
        g_signal_connect (mi, "scroll-event", G_CALLBACK (range_scroll_cb), button);
        g_signal_connect (menu, "show", G_CALLBACK (range_show_cb), button);

        gtk_widget_show_all (mi);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
    }

    /* Presentation mode checkbox */
    mi = gtk_check_menu_item_new_with_mnemonic (_("Presentation _mode"));
    gtk_widget_set_sensitive (mi, TRUE);
    gtk_widget_show (mi);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
    xfconf_g_property_bind(button->priv->channel,
                           PROPERTIES_PREFIX PRESENTATION_MODE,
                           G_TYPE_BOOLEAN, G_OBJECT(mi), "active");

    /* Power manager settings */
    mi = gtk_menu_item_new_with_mnemonic (_("_Power manager settings..."));
    gtk_widget_show(mi);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
    g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(xfpm_preferences), NULL);

    gtk_menu_popup (GTK_MENU (menu),
                    NULL,
                    NULL,
#ifdef XFCE_PLUGIN
                    xfce_panel_plugin_position_menu,
#else
                    NULL,
#endif
#ifdef XFCE_PLUGIN
                    button->priv->plugin,
#else
                    NULL,
#endif
                    0,
                    gtk_get_current_event_time ());
}