/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpwidgets-utils.c
 * Copyright (C) 1999-2003 Michael Natterer <mitch@gimp.org>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gtk/gtk.h>

#ifdef GDK_WINDOWING_WIN32
#include <gdk/gdkwin32.h>
#endif

#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif

#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

#include "gimpdialogfactory.h"
#include "gimpdock.h"
#include "gimpdockwindow.h"
#include "gimperrordialog.h"
#include "gimpwidgets-utils.h"

#include "gimp-intl.h"


#define GIMP_TOOL_OPTIONS_GUI_KEY "gimp-tool-options-gui"


/**
 * gimp_menu_position:
 * @menu: a #GtkMenu widget
 * @x: pointer to horizontal position
 * @y: pointer to vertical position
 *
 * Positions a #GtkMenu so that it pops up on screen.  This function
 * takes care of the preferred popup direction (taken from the widget
 * render direction) and it handles multiple monitors representing a
 * single #GdkScreen (Xinerama).
 *
 * You should call this function with @x and @y initialized to the
 * origin of the menu. This is typically the center of the widget the
 * menu is popped up from. gimp_menu_position() will then decide if
 * and how these initial values need to be changed.
 **/
void
gimp_menu_position (GtkMenu *menu,
                    gint    *x,
                    gint    *y)
{
  GtkWidget      *widget;
  GdkScreen      *screen;
  GtkRequisition  requisition;
  GdkRectangle    rect;
  gint            monitor;

  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (x != NULL);
  g_return_if_fail (y != NULL);

  widget = GTK_WIDGET (menu);

  screen = gtk_widget_get_screen (widget);

  monitor = gdk_screen_get_monitor_at_point (screen, *x, *y);
  gdk_screen_get_monitor_geometry (screen, monitor, &rect);

  gtk_menu_set_screen (menu, screen);

  gtk_widget_size_request (widget, &requisition);

  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
    {
      *x -= requisition.width;
      if (*x < rect.x)
        *x += requisition.width;
    }
  else
    {
      if (*x + requisition.width > rect.x + rect.width)
        *x -= requisition.width;
    }

  if (*x < rect.x)
    *x = rect.x;

  if (*y + requisition.height > rect.y + rect.height)
    *y -= requisition.height;

  if (*y < rect.y)
    *y = rect.y;
}

/**
 * gimp_button_menu_position:
 * @button: a button widget to popup the menu from
 * @menu: the menu to position
 * @position: the preferred popup direction for the menu (left or right)
 * @x: return location for x coordinate
 * @y: return location for y coordinate
 *
 * Utility function to position a menu that pops up from a button.
 **/
void
gimp_button_menu_position (GtkWidget       *button,
                           GtkMenu         *menu,
                           GtkPositionType  position,
                           gint            *x,
                           gint            *y)
{
  GdkScreen      *screen;
  GtkAllocation   button_allocation;
  GtkRequisition  menu_requisition;
  GdkRectangle    rect;
  gint            monitor;

  g_return_if_fail (GTK_IS_WIDGET (button));
  g_return_if_fail (gtk_widget_get_realized (button));
  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (x != NULL);
  g_return_if_fail (y != NULL);

  gtk_widget_get_allocation (button, &button_allocation);

  if (gtk_widget_get_direction (button) == GTK_TEXT_DIR_RTL)
    {
      switch (position)
        {
        case GTK_POS_LEFT:   position = GTK_POS_RIGHT;  break;
        case GTK_POS_RIGHT:  position = GTK_POS_LEFT;   break;
        default:
          break;
        }
    }

  *x = 0;
  *y = 0;

  if (! gtk_widget_get_has_window (button))
    {
      *x += button_allocation.x;
      *y += button_allocation.y;
    }

  gdk_window_get_root_coords (gtk_widget_get_window (button), *x, *y, x, y);

  gtk_widget_size_request (GTK_WIDGET (menu), &menu_requisition);

  screen = gtk_widget_get_screen (button);

  monitor = gdk_screen_get_monitor_at_point (screen, *x, *y);
  gdk_screen_get_monitor_geometry (screen, monitor, &rect);

  gtk_menu_set_screen (menu, screen);

  switch (position)
    {
    case GTK_POS_LEFT:
      *x -= menu_requisition.width;
      if (*x < rect.x)
        *x += menu_requisition.width + button_allocation.width;
      break;

    case GTK_POS_RIGHT:
      *x += button_allocation.width;
      if (*x + menu_requisition.width > rect.x + rect.width)
        *x -= button_allocation.width + menu_requisition.width;
      break;

    default:
      g_warning ("%s: unhandled position (%d)", G_STRFUNC, position);
      break;
    }

  *y += button_allocation.height / 2;

  if (*y + menu_requisition.height > rect.y + rect.height)
    *y -= menu_requisition.height;

  if (*y < rect.y)
    *y = rect.y;
}

void
gimp_table_attach_stock (GtkTable    *table,
                         gint         row,
                         const gchar *stock_id,
                         GtkWidget   *widget,
                         gint         colspan,
                         gboolean     left_align)
{
  GtkWidget *image;

  g_return_if_fail (GTK_IS_TABLE (table));
  g_return_if_fail (stock_id != NULL);
  g_return_if_fail (GTK_IS_WIDGET (widget));

  image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
  gtk_misc_set_alignment (GTK_MISC (image), 1.0, 0.5);
  gtk_table_attach (table, image, 0, 1, row, row + 1,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (image);

  if (left_align)
    {
      GtkWidget *hbox = gtk_hbox_new (FALSE, 0);

      gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
      gtk_widget_show (widget);

      widget = hbox;
    }

  gtk_table_attach (table, widget, 1, 1 + colspan, row, row + 1,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_show (widget);
}

void
gimp_enum_radio_box_add (GtkBox    *box,
                         GtkWidget *widget,
                         gint       enum_value,
                         gboolean   below)
{
  GList *children;
  GList *list;
  gint   pos;

  g_return_if_fail (GTK_IS_BOX (box));
  g_return_if_fail (GTK_IS_WIDGET (widget));

  children = gtk_container_get_children (GTK_CONTAINER (box));

  for (list = children, pos = 1;
       list;
       list = g_list_next (list), pos++)
    {
      if (GTK_IS_RADIO_BUTTON (list->data) &&
          GPOINTER_TO_INT (g_object_get_data (list->data, "gimp-item-data")) ==
          enum_value)
        {
          GtkWidget *radio = list->data;
          GtkWidget *hbox;

          hbox = gtk_hbox_new (FALSE, 0);
          gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);
          gtk_box_reorder_child (GTK_BOX (box), hbox, pos);

          if (below)
            {
              GtkWidget *spacer;
              gint       indicator_size;
              gint       indicator_spacing;
              gint       focus_width;
              gint       focus_padding;
              gint       border_width;

              gtk_widget_style_get (radio,
                                    "indicator-size",    &indicator_size,
                                    "indicator-spacing", &indicator_spacing,
                                    "focus-line-width",  &focus_width,
                                    "focus-padding",     &focus_padding,
                                    NULL);

              border_width = gtk_container_get_border_width (GTK_CONTAINER (radio));

              spacer = gtk_vbox_new (FALSE, 0);
              gtk_widget_set_size_request (spacer,
                                           indicator_size +
                                           3 * indicator_spacing +
                                           focus_width +
                                           focus_padding +
                                           border_width,
                                           -1);
              gtk_box_pack_start (GTK_BOX (hbox), spacer, FALSE, FALSE, 0);
              gtk_widget_show (spacer);
            }
          else
            {
              GtkSizeGroup *size_group;

              size_group = g_object_get_data (G_OBJECT (box), "size-group");

              if (! size_group)
                {
                  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
                  g_object_set_data (G_OBJECT (box), "size-group", size_group);

                  gtk_size_group_add_widget (size_group, radio);
                  g_object_unref (size_group);
                }
              else
                {
                  gtk_size_group_add_widget (size_group, radio);
                }

              gtk_box_set_spacing (GTK_BOX (hbox), 4);

              g_object_ref (radio);
              gtk_container_remove (GTK_CONTAINER (box), radio);
              gtk_box_pack_start (GTK_BOX (hbox), radio, FALSE, FALSE, 0);
              g_object_unref (radio);
            }

          gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
          gtk_widget_show (widget);

          g_object_set_data (G_OBJECT (radio), "set_sensitive", widget);
          g_signal_connect (radio, "toggled",
                            G_CALLBACK (gimp_toggle_button_sensitive_update),
                            NULL);

          gtk_widget_set_sensitive (widget,
                                    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (list->data)));

          gtk_widget_show (hbox);

          break;
        }
    }

  g_list_free (children);
}

void
gimp_enum_radio_frame_add (GtkFrame  *frame,
                           GtkWidget *widget,
                           gint       enum_value,
                           gboolean   below)
{
  GtkWidget *vbox;

  g_return_if_fail (GTK_IS_FRAME (frame));
  g_return_if_fail (GTK_IS_WIDGET (widget));

  vbox = gtk_bin_get_child (GTK_BIN (frame));

  g_return_if_fail (GTK_IS_VBOX (vbox));

  gimp_enum_radio_box_add (GTK_BOX (vbox), widget, enum_value, below);
}

GtkIconSize
gimp_get_icon_size (GtkWidget   *widget,
                    const gchar *stock_id,
                    GtkIconSize  max_size,
                    gint         width,
                    gint         height)
{
  GtkIconSet   *icon_set;
  GtkIconSize  *sizes;
  gint          n_sizes;
  gint          i;
  gint          width_diff  = 1024;
  gint          height_diff = 1024;
  gint          max_width;
  gint          max_height;
  GtkIconSize   icon_size = GTK_ICON_SIZE_MENU;
  GtkSettings  *settings;

  g_return_val_if_fail (GTK_IS_WIDGET (widget), icon_size);
  g_return_val_if_fail (stock_id != NULL, icon_size);
  g_return_val_if_fail (width > 0, icon_size);
  g_return_val_if_fail (height > 0, icon_size);

  icon_set = gtk_style_lookup_icon_set (gtk_widget_get_style (widget),
                                        stock_id);

  if (! icon_set)
    return GTK_ICON_SIZE_INVALID;

  settings = gtk_widget_get_settings (widget);

  if (! gtk_icon_size_lookup_for_settings (settings, max_size,
                                           &max_width, &max_height))
    {
      max_width  = 1024;
      max_height = 1024;
    }

  gtk_icon_set_get_sizes (icon_set, &sizes, &n_sizes);

  for (i = 0; i < n_sizes; i++)
    {
      gint icon_width;
      gint icon_height;

      if (gtk_icon_size_lookup_for_settings (settings, sizes[i],
                                             &icon_width, &icon_height))
        {
          if (icon_width  <= width      &&
              icon_height <= height     &&
              icon_width  <= max_width  &&
              icon_height <= max_height &&
              ((width  - icon_width)  < width_diff ||
               (height - icon_height) < height_diff))
            {
              width_diff  = width  - icon_width;
              height_diff = height - icon_height;

              icon_size = sizes[i];
            }
        }
    }

  g_free (sizes);

  return icon_size;
}

GimpTabStyle
gimp_preview_tab_style_to_icon (GimpTabStyle tab_style)
{
  switch (tab_style)
    {
    case GIMP_TAB_STYLE_PREVIEW:
      tab_style = GIMP_TAB_STYLE_ICON;
      break;

    case GIMP_TAB_STYLE_PREVIEW_NAME:
      tab_style = GIMP_TAB_STYLE_ICON_NAME;
      break;

    case GIMP_TAB_STYLE_PREVIEW_BLURB:
      tab_style = GIMP_TAB_STYLE_ICON_BLURB;
      break;

    default:
      break;
    }

  return tab_style;
}

const gchar *
gimp_get_mod_name_shift (void)
{
  static gchar *mod_name_shift = NULL;

  if (! mod_name_shift)
    {
      GtkAccelLabelClass *accel_label_class;

      accel_label_class = g_type_class_ref (GTK_TYPE_ACCEL_LABEL);
      mod_name_shift = g_strdup (accel_label_class->mod_name_shift);
      g_type_class_unref (accel_label_class);
    }

  return (const gchar *) mod_name_shift;
}

const gchar *
gimp_get_mod_name_control (void)
{
  static gchar *mod_name_control = NULL;

  if (! mod_name_control)
    {
      GtkAccelLabelClass *accel_label_class;

      accel_label_class = g_type_class_ref (GTK_TYPE_ACCEL_LABEL);
      mod_name_control = g_strdup (accel_label_class->mod_name_control);
      g_type_class_unref (accel_label_class);
    }

  return (const gchar *) mod_name_control;
}

const gchar *
gimp_get_mod_name_alt (void)
{
  static gchar *mod_name_alt = NULL;

  if (! mod_name_alt)
    {
      GtkAccelLabelClass *accel_label_class;

      accel_label_class = g_type_class_ref (GTK_TYPE_ACCEL_LABEL);
      mod_name_alt = g_strdup (accel_label_class->mod_name_alt);
      g_type_class_unref (accel_label_class);
    }

  return (const gchar *) mod_name_alt;
}

const gchar *
gimp_get_mod_separator (void)
{
  static gchar *mod_separator = NULL;

  if (! mod_separator)
    {
      GtkAccelLabelClass *accel_label_class;

      accel_label_class = g_type_class_ref (GTK_TYPE_ACCEL_LABEL);
      mod_separator = g_strdup (accel_label_class->mod_separator);
      g_type_class_unref (accel_label_class);
    }

  return (const gchar *) mod_separator;
}

const gchar *
gimp_get_mod_string (GdkModifierType modifiers)
{
  static struct
  {
    GdkModifierType  modifiers;
    gchar           *name;
  }
  modifier_strings[] =
  {
    { GDK_SHIFT_MASK,                                      NULL },
    { GDK_CONTROL_MASK,                                    NULL },
    { GDK_MOD1_MASK,                                       NULL },
    { GDK_SHIFT_MASK   | GDK_CONTROL_MASK,                 NULL },
    { GDK_SHIFT_MASK   | GDK_MOD1_MASK,                    NULL },
    { GDK_CONTROL_MASK | GDK_MOD1_MASK,                    NULL },
    { GDK_SHIFT_MASK   | GDK_CONTROL_MASK | GDK_MOD1_MASK, NULL }
  };

  gint i;

  for (i = 0; i < G_N_ELEMENTS (modifier_strings); i++)
    {
      if (modifiers == modifier_strings[i].modifiers)
        {
          if (! modifier_strings[i].name)
            {
              GString *str = g_string_new (NULL);

              if (modifiers & GDK_SHIFT_MASK)
                {
                  g_string_append (str, gimp_get_mod_name_shift ());
                }

              if (modifiers & GDK_CONTROL_MASK)
                {
                  if (str->len)
                    g_string_append (str, gimp_get_mod_separator ());

                  g_string_append (str, gimp_get_mod_name_control ());
                }

              if (modifiers & GDK_MOD1_MASK)
                {
                  if (str->len)
                    g_string_append (str, gimp_get_mod_separator ());

                  g_string_append (str, gimp_get_mod_name_alt ());
                }

              modifier_strings[i].name = g_string_free (str, FALSE);
            }

          return modifier_strings[i].name;
        }
    }

  return NULL;
}

#define BUF_SIZE 100
/**
 * gimp_suggest_modifiers:
 * @message: initial text for the message
 * @modifiers: bit mask of modifiers that should be suggested
 * @shift_format: optional format string for the Shift modifier
 * @control_format: optional format string for the Ctrl modifier
 * @alt_format: optional format string for the Alt modifier
 *
 * Utility function to build a message suggesting to use some
 * modifiers for performing different actions (only Shift, Ctrl and
 * Alt are currently supported).  If some of these modifiers are
 * already active, they will not be suggested.  The optional format
 * strings #shift_format, #control_format and #alt_format may be used
 * to describe what the modifier will do.  They must contain a single
 * '%%s' which will be replaced by the name of the modifier.  They
 * can also be %NULL if the modifier name should be left alone.
 *
 * Return value: a newly allocated string containing the message.
 **/
gchar *
gimp_suggest_modifiers (const gchar     *message,
                        GdkModifierType  modifiers,
                        const gchar     *shift_format,
                        const gchar     *control_format,
                        const gchar     *alt_format)
{
  gchar     msg_buf[3][BUF_SIZE];
  gint      num_msgs = 0;
  gboolean  try      = FALSE;

  if (modifiers & GDK_SHIFT_MASK)
    {
      if (shift_format && *shift_format)
        {
          g_snprintf (msg_buf[num_msgs], BUF_SIZE, shift_format,
                      gimp_get_mod_name_shift ());
        }
      else
        {
          g_strlcpy (msg_buf[num_msgs], gimp_get_mod_name_shift (), BUF_SIZE);
          try = TRUE;
        }

      num_msgs++;
    }

  if (modifiers & GDK_CONTROL_MASK)
    {
      if (control_format && *control_format)
        {
          g_snprintf (msg_buf[num_msgs], BUF_SIZE, control_format,
                      gimp_get_mod_name_control ());
        }
      else
        {
          g_strlcpy (msg_buf[num_msgs], gimp_get_mod_name_control (), BUF_SIZE);
          try = TRUE;
        }

      num_msgs++;
    }

  if (modifiers & GDK_MOD1_MASK)
    {
      if (alt_format && *alt_format)
        {
          g_snprintf (msg_buf[num_msgs], BUF_SIZE, alt_format,
                      gimp_get_mod_name_alt ());
        }
      else
        {
          g_strlcpy (msg_buf[num_msgs], gimp_get_mod_name_alt (), BUF_SIZE);
          try = TRUE;
        }

      num_msgs++;
    }

  /* This convoluted way to build the message using multiple format strings
   * tries to make the messages easier to translate to other languages.
   */

  switch (num_msgs)
    {
    case 1:
      return g_strdup_printf (try ? _("%s (try %s)") : _("%s (%s)"),
                              message, msg_buf[0]);

    case 2:
      return g_strdup_printf (_("%s (try %s, %s)"),
                              message, msg_buf[0], msg_buf[1]);

    case 3:
      return g_strdup_printf (_("%s (try %s, %s, %s)"),
                              message, msg_buf[0], msg_buf[1], msg_buf[2]);
    }

  return g_strdup (message);
}
#undef BUF_SIZE

/**
 * gimp_get_screen_resolution:
 * @screen: a #GdkScreen or %NULL
 * @xres: returns the horizontal screen resolution (in dpi)
 * @yres: returns the vertical screen resolution (in dpi)
 *
 * Retrieves the screen resolution from GDK. If @screen is %NULL, the
 * default screen is used.
 **/
void
gimp_get_screen_resolution (GdkScreen *screen,
                            gdouble   *xres,
                            gdouble   *yres)
{
  gint    width, height;
  gint    width_mm, height_mm;
  gdouble x = 0.0;
  gdouble y = 0.0;

  g_return_if_fail (screen == NULL || GDK_IS_SCREEN (screen));
  g_return_if_fail (xres != NULL);
  g_return_if_fail (yres != NULL);

  if (!screen)
    screen = gdk_screen_get_default ();

  width  = gdk_screen_get_width (screen);
  height = gdk_screen_get_height (screen);

  width_mm  = gdk_screen_get_width_mm (screen);
  height_mm = gdk_screen_get_height_mm (screen);

  /*
   * From xdpyinfo.c:
   *
   * there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
   *
   *     dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
   *         = N pixels / (M inch / 25.4)
   *         = N * 25.4 pixels / M inch
   */

  if (width_mm > 0 && height_mm > 0)
    {
      x = (width  * 25.4) / (gdouble) width_mm;
      y = (height * 25.4) / (gdouble) height_mm;
    }

  if (x < GIMP_MIN_RESOLUTION || x > GIMP_MAX_RESOLUTION ||
      y < GIMP_MIN_RESOLUTION || y > GIMP_MAX_RESOLUTION)
    {
      g_warning ("GDK returned bogus values for the screen resolution, "
                 "using 96 dpi instead.");

      x = 96.0;
      y = 96.0;
    }

  /*  round the value to full integers to give more pleasant results  */
  *xres = ROUND (x);
  *yres = ROUND (y);
}


/**
 * gimp_rgb_get_gdk_color:
 * @rgb: the source color as #GimpRGB
 * @gdk_color: pointer to a #GdkColor
 *
 * Initializes @gdk_color from a #GimpRGB. This function does not
 * allocate the color for you. Depending on how you want to use it,
 * you may have to call gdk_colormap_alloc_color().
 **/
void
gimp_rgb_get_gdk_color (const GimpRGB *rgb,
                        GdkColor      *gdk_color)
{
  guchar r, g, b;

  g_return_if_fail (rgb != NULL);
  g_return_if_fail (gdk_color != NULL);

  gimp_rgb_get_uchar (rgb, &r, &g, &b);

  gdk_color->red   = (r << 8) | r;
  gdk_color->green = (g << 8) | g;
  gdk_color->blue  = (b << 8) | b;
}

/**
 * gimp_rgb_set_gdk_color:
 * @rgb: a #GimpRGB that is to be set
 * @gdk_color: pointer to the source #GdkColor
 *
 * Initializes @rgb from a #GdkColor. This function does not touch
 * the alpha value of @rgb.
 **/
void
gimp_rgb_set_gdk_color (GimpRGB        *rgb,
                        const GdkColor *gdk_color)
{
  guchar r, g, b;

  g_return_if_fail (rgb != NULL);
  g_return_if_fail (gdk_color != NULL);

  r = gdk_color->red   >> 8;
  g = gdk_color->green >> 8;
  b = gdk_color->blue  >> 8;

  gimp_rgb_set_uchar (rgb, r, g, b);
}

void
gimp_window_set_hint (GtkWindow      *window,
                      GimpWindowHint  hint)
{
  g_return_if_fail (GTK_IS_WINDOW (window));

  switch (hint)
    {
    case GIMP_WINDOW_HINT_NORMAL:
      gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_NORMAL);
      break;

    case GIMP_WINDOW_HINT_UTILITY:
      gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_UTILITY);
      break;

    case GIMP_WINDOW_HINT_KEEP_ABOVE:
      gtk_window_set_keep_above (window, TRUE);
      break;
    }
}

/**
 * gimp_window_get_native:
 * @window: a #GtkWindow
 *
 * This function is used to pass a window handle to plug-ins so that
 * they can set their dialog windows transient to the parent window.
 *
 * Return value: a native window handle of the window's #GdkWindow or 0
 *               if the window isn't realized yet
 */
GdkNativeWindow
gimp_window_get_native (GtkWindow *window)
{
  g_return_val_if_fail (GTK_IS_WINDOW (window), 0);

#ifdef GDK_NATIVE_WINDOW_POINTER
#ifdef __GNUC__
#warning gimp_window_get_native() unimplementable for the target windowing system
#endif
  return (GdkNativeWindow)0;
#endif

#ifdef GDK_WINDOWING_WIN32
  if (window && gtk_widget_get_realized (GTK_WIDGET (window)))
    return (GdkNativeWindow) GDK_WINDOW_HWND (gtk_widget_get_window (GTK_WIDGET (window)));
#endif

#ifdef GDK_WINDOWING_X11
  if (window && gtk_widget_get_realized (GTK_WIDGET (window)))
    return GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (window)));
#endif

  return (GdkNativeWindow) 0;
}

static void
gimp_window_transient_realized (GtkWidget *window,
                                GdkWindow *parent)
{
  if (gtk_widget_get_realized (window))
    gdk_window_set_transient_for (gtk_widget_get_window (window), parent);
}

/* similar to what we have in libgimp/gimpui.c */
void
gimp_window_set_transient_for (GtkWindow *window,
                               guint32    parent_ID)
{
  /* Cross-process transient-for is broken in gdk/win32 <= 2.10.6. It
   * causes hangs, at least when used as by the gimp and script-fu
   * processes. In some newer GTK+ version it will be fixed to be a
   * no-op. If it eventually is fixed to actually work, change this to
   * a run-time check of GTK+ version. Remember to change also the
   * function with the same name in libgimp/gimpui.c
   */
#ifndef GDK_WINDOWING_WIN32
  GdkWindow *parent;

  parent = gdk_window_foreign_new_for_display (gdk_display_get_default (),
                                               parent_ID);

  if (! parent)
    return;

  if (gtk_widget_get_realized (GTK_WIDGET (window)))
    gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (window)),
                                  parent);

  g_signal_connect_object (window, "realize",
                           G_CALLBACK (gimp_window_transient_realized),
                           parent, 0);

  g_object_unref (parent);
#endif
}

void
gimp_toggle_button_set_visible (GtkToggleButton *toggle,
                                GtkWidget       *widget)
{
  g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle));
  g_return_if_fail (GTK_IS_WIDGET (widget));

  gtk_widget_set_visible (widget,
                          gtk_toggle_button_get_active (toggle));
}

static gboolean
gimp_widget_accel_find_func (GtkAccelKey *key,
                             GClosure    *closure,
                             gpointer     data)
{
  return (GClosure *) data == closure;
}

static void
gimp_widget_accel_changed (GtkAccelGroup   *accel_group,
                           guint            unused1,
                           GdkModifierType  unused2,
                           GClosure        *accel_closure,
                           GtkWidget       *widget)
{
  GClosure *widget_closure;

  widget_closure = g_object_get_data (G_OBJECT (widget), "gimp-accel-closure");

  if (accel_closure == widget_closure)
    {
      GtkAction   *action;
      GtkAccelKey *accel_key;
      const gchar *tooltip;
      const gchar *help_id;

      action = g_object_get_data (G_OBJECT (widget), "gimp-accel-action");

      tooltip = gtk_action_get_tooltip (action);
      help_id = g_object_get_qdata (G_OBJECT (action), GIMP_HELP_ID);

      accel_key = gtk_accel_group_find (accel_group,
                                        gimp_widget_accel_find_func,
                                        accel_closure);

      if (accel_key            &&
          accel_key->accel_key &&
          accel_key->accel_flags & GTK_ACCEL_VISIBLE)
        {
          gchar *escaped = g_markup_escape_text (tooltip, -1);
          gchar *accel   = gtk_accelerator_get_label (accel_key->accel_key,
                                                      accel_key->accel_mods);
          gchar *tmp     = g_strdup_printf ("%s  <b>%s</b>", escaped, accel);

          g_free (accel);
          g_free (escaped);

          gimp_help_set_help_data_with_markup (widget, tmp, help_id);
          g_free (tmp);
        }
      else
        {
          gimp_help_set_help_data (widget, tooltip, help_id);
        }
    }
}

void
gimp_widget_set_accel_help (GtkWidget *widget,
                            GtkAction *action)
{
  GClosure *accel_closure = gtk_action_get_accel_closure (action);

  if (accel_closure)
    {
      GtkAccelGroup *accel_group;

      g_object_set_data (G_OBJECT (widget), "gimp-accel-closure",
                         accel_closure);
      g_object_set_data (G_OBJECT (widget), "gimp-accel-action",
                         action);

      accel_group = gtk_accel_group_from_accel_closure (accel_closure);

      g_signal_connect_object (accel_group, "accel-changed",
                               G_CALLBACK (gimp_widget_accel_changed),
                               widget, 0);

      gimp_widget_accel_changed (accel_group,
                                 0, 0,
                                 accel_closure,
                                 widget);
    }
  else
    {
      const gchar *tooltip;
      const gchar *help_id;

      tooltip = gtk_action_get_tooltip (action);
      help_id = g_object_get_qdata (G_OBJECT (action), GIMP_HELP_ID);

      gimp_help_set_help_data (widget, tooltip, help_id);
    }
}

const gchar *
gimp_get_message_stock_id (GimpMessageSeverity severity)
{
  switch (severity)
    {
    case GIMP_MESSAGE_INFO:
      return GIMP_STOCK_INFO;

    case GIMP_MESSAGE_WARNING:
      return GIMP_STOCK_WARNING;

    case GIMP_MESSAGE_ERROR:
      return GIMP_STOCK_ERROR;
    }

  g_return_val_if_reached (GIMP_STOCK_WARNING);
}

void
gimp_pango_layout_set_scale (PangoLayout *layout,
                             gdouble      scale)
{
  PangoAttrList  *attrs;
  PangoAttribute *attr;

  g_return_if_fail (PANGO_IS_LAYOUT (layout));

  attrs = pango_attr_list_new ();

  attr = pango_attr_scale_new (scale);
  attr->start_index = 0;
  attr->end_index   = -1;
  pango_attr_list_insert (attrs, attr);

  pango_layout_set_attributes (layout, attrs);
  pango_attr_list_unref (attrs);
}

void
gimp_pango_layout_set_weight (PangoLayout *layout,
                              PangoWeight  weight)
{
  PangoAttrList  *attrs;
  PangoAttribute *attr;

  g_return_if_fail (PANGO_IS_LAYOUT (layout));

  attrs = pango_attr_list_new ();

  attr = pango_attr_weight_new (weight);
  attr->start_index = 0;
  attr->end_index   = -1;
  pango_attr_list_insert (attrs, attr);

  pango_layout_set_attributes (layout, attrs);
  pango_attr_list_unref (attrs);
}

/**
 * gimp_highlight_widget:
 * @widget:
 * @highlight:
 *
 * Calls gtk_drag_highlight() on @widget if @highlight is %TRUE,
 * calls gtk_drag_unhighlight() otherwise.
 **/
void
gimp_highlight_widget (GtkWidget *widget,
                       gboolean   highlight)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));

  if (highlight)
    gtk_drag_highlight (widget);
  else
    gtk_drag_unhighlight (widget);
}

/**
 * gimp_dock_with_window_new:
 * @factory: a #GimpDialogFacotry
 * @screen:  the #GdkScreen the dock window should appear on
 * @toolbox: if %TRUE; gives a "gimp-toolbox-window" with a
 *           "gimp-toolbox", "gimp-dock-window"+"gimp-dock"
 *           otherwise
 *
 * Returns: the newly created #GimpDock with the #GimpDockWindow
 **/
GtkWidget *
gimp_dock_with_window_new (GimpDialogFactory *factory,
                           GdkScreen         *screen,
                           gboolean           toolbox)
{
  GtkWidget     *dock_window = NULL;
  GtkWidget     *dock        = NULL;
  GimpUIManager *ui_manager  = NULL;

  g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

  /* Create a dock window to put the dock in. We need to create the
   * dock window before the dock because the dock has a dependency to
   * the ui manager in the dock window
   */
  dock_window =
    gimp_dialog_factory_dialog_new (factory,
                                    screen,
                                    NULL /*ui_manager*/,
                                    (toolbox ?
                                     "gimp-toolbox-window" :
                                     "gimp-dock-window"),
                                    -1 /*view_size*/,
                                    FALSE /*present*/);

  /* Create the dock */
  ui_manager = gimp_dock_window_get_ui_manager (GIMP_DOCK_WINDOW (dock_window));
  dock       = gimp_dialog_factory_dialog_new (factory,
                                               screen,
                                               ui_manager,
                                               (toolbox ?
                                                "gimp-toolbox" :
                                                "gimp-dock"),
                                               -1 /*view_size*/,
                                               FALSE /*present*/);
  if (dock)
    {
      /* Put the dock in the dock window */
      gimp_dock_window_add_dock (GIMP_DOCK_WINDOW (dock_window),
                                 GIMP_DOCK (dock),
                                 -1);
    }

  return dock;
}

GtkWidget *
gimp_tools_get_tool_options_gui (GimpToolOptions *tool_options)
{
  return g_object_get_data (G_OBJECT (tool_options),
                            GIMP_TOOL_OPTIONS_GUI_KEY);
}

void
gimp_tools_set_tool_options_gui (GimpToolOptions   *tool_options,
                                 GtkWidget         *widget)
{
  g_object_set_data_full (G_OBJECT (tool_options),
                          GIMP_TOOL_OPTIONS_GUI_KEY,
                          widget,
                          widget ? (GDestroyNotify) g_object_unref : NULL);
}

void
gimp_widget_flush_expose (GtkWidget *widget)
{
  GList *event_list = NULL;

  g_return_if_fail (GTK_IS_WIDGET (widget));

  if (! gtk_widget_is_drawable (widget))
    return;

  gdk_window_process_updates (gtk_widget_get_window (widget), FALSE);

  while (gdk_events_pending ())
    {
      GdkEvent *event = gdk_event_get ();

      if (! event)
        break;

      if (gtk_get_event_widget (event) == widget &&
          event->any.type == GDK_EXPOSE)
        {
          if (gtk_widget_get_double_buffered (widget))
            {
              gdk_window_begin_paint_region (event->any.window,
                                             event->expose.region);
              gtk_widget_send_expose (widget, event);
              gdk_window_end_paint (event->any.window);
            }
          else
            {
              gdk_window_flush (event->any.window);
              gtk_widget_send_expose (widget, event);
            }

          gdk_event_free (event);
        }
      else
        {
          event_list = g_list_prepend (event_list, event);
        }
    }

  event_list = g_list_reverse (event_list);

  g_list_foreach (event_list, (GFunc) gdk_event_put, NULL);
  g_list_foreach (event_list, (GFunc) gdk_event_free, NULL);

  g_list_free (event_list);
}
