gtkutil.c 163 KB
Newer Older
Jan Djärv's avatar
Jan Djärv committed
1
/* Functions for creating and updating GTK widgets.
2

Paul Eggert's avatar
Paul Eggert committed
3
Copyright (C) 2003-2018 Free Software Foundation, Inc.
Jan Djärv's avatar
Jan Djärv committed
4 5 6

This file is part of GNU Emacs.

7
GNU Emacs is free software: you can redistribute it and/or modify
Jan Djärv's avatar
Jan Djärv committed
8
it under the terms of the GNU General Public License as published by
9 10
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
Jan Djärv's avatar
Jan Djärv committed
11 12 13 14 15 16 17

GNU Emacs 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
18
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
Jan Djärv's avatar
Jan Djärv committed
19

20
#include <config.h>
Jan Djärv's avatar
Jan Djärv committed
21 22

#ifdef USE_GTK
23
#include <float.h>
24
#include <stdio.h>
25 26 27

#include <c-ctype.h>

Jan Djärv's avatar
Jan Djärv committed
28
#include "lisp.h"
29 30 31
#include "dispextern.h"
#include "frame.h"
#include "systime.h"
Jan Djärv's avatar
Jan Djärv committed
32 33 34 35 36
#include "xterm.h"
#include "blockinput.h"
#include "window.h"
#include "gtkutil.h"
#include "termhooks.h"
37 38
#include "keyboard.h"
#include "coding.h"
39

Jan Djärv's avatar
Jan Djärv committed
40 41
#include <gdk/gdkkeysyms.h>

42 43 44
#ifdef HAVE_XFT
#include <X11/Xft/Xft.h>
#endif
45

46 47
#ifdef HAVE_GTK3
#include <gtk/gtkx.h>
48
#include "emacsgtkfixed.h"
49 50
#endif

51 52 53 54
#ifdef HAVE_XDBE
#include <X11/extensions/Xdbe.h>
#endif

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
#ifndef HAVE_GTK_WIDGET_SET_HAS_WINDOW
#define gtk_widget_set_has_window(w, b) \
  (gtk_fixed_set_has_window (GTK_FIXED (w), b))
#endif
#ifndef HAVE_GTK_DIALOG_GET_ACTION_AREA
#define gtk_dialog_get_action_area(w) ((w)->action_area)
#define gtk_dialog_get_content_area(w) ((w)->vbox)
#endif
#ifndef HAVE_GTK_WIDGET_GET_SENSITIVE
#define gtk_widget_get_sensitive(w) (GTK_WIDGET_SENSITIVE (w))
#endif
#ifndef HAVE_GTK_ADJUSTMENT_GET_PAGE_SIZE
#define gtk_adjustment_set_page_size(w, s) ((w)->page_size = (s))
#define gtk_adjustment_set_page_increment(w, s) ((w)->page_increment = (s))
#define gtk_adjustment_get_step_increment(w) ((w)->step_increment)
#define gtk_adjustment_set_step_increment(w, s) ((w)->step_increment = (s))
#endif
72
#if GTK_CHECK_VERSION (2, 12, 0)
73 74 75 76 77
#define remove_submenu(w) gtk_menu_item_set_submenu ((w), NULL)
#else
#define remove_submenu(w) gtk_menu_item_remove_submenu ((w))
#endif

Jan D's avatar
Jan D committed
78
#if ! GTK_CHECK_VERSION (2, 14, 0)
Jan D's avatar
Jan D committed
79 80 81 82 83 84 85 86 87 88
#define gtk_adjustment_configure(adj, xvalue, xlower,            \
                                 xupper, xstep_increment,        \
                                 xpage_increment, xpagesize)     \
  do {                                                           \
    adj->lower = xlower;                                         \
    adj->upper = xupper;                                         \
    adj->page_size = xpagesize;                                  \
    gtk_adjustment_set_value (adj, xvalue);                      \
    adj->page_increment = xpage_increment;                       \
    adj->step_increment = xstep_increment;                       \
Jan D's avatar
Jan D committed
89 90 91
  } while (0)
#endif /* < Gtk+ 2.14 */

92
#ifdef HAVE_FREETYPE
93
#if GTK_CHECK_VERSION (3, 2, 0)
94 95 96
#define USE_NEW_GTK_FONT_CHOOSER 1
#else
#define USE_NEW_GTK_FONT_CHOOSER 0
97 98 99 100 101 102 103
#define gtk_font_chooser_dialog_new(x, y) \
  gtk_font_selection_dialog_new (x)
#undef GTK_FONT_CHOOSER
#define GTK_FONT_CHOOSER(x) GTK_FONT_SELECTION_DIALOG (x)
#define  gtk_font_chooser_set_font(x, y) \
  gtk_font_selection_dialog_set_font_name (x, y)
#endif
104
#endif /* HAVE_FREETYPE */
105

106 107 108 109 110 111 112 113 114 115
#if GTK_CHECK_VERSION (3, 10, 0)
#define XG_TEXT_CANCEL "Cancel"
#define XG_TEXT_OK     "OK"
#define XG_TEXT_OPEN   "Open"
#else
#define XG_TEXT_CANCEL GTK_STOCK_CANCEL
#define XG_TEXT_OK     GTK_STOCK_OK
#define XG_TEXT_OPEN   GTK_STOCK_OPEN
#endif

116
#ifndef HAVE_GTK3
117
#ifdef USE_GTK_TOOLTIP
118
#define gdk_window_get_screen(w) gdk_drawable_get_screen (w)
119
#endif
120
#define gdk_window_get_geometry(w, a, b, c, d) \
121
  gdk_window_get_geometry (w, a, b, c, d, 0)
122 123
#define gdk_x11_window_lookup_for_display(d, w) \
  gdk_xid_table_lookup_for_display (d, w)
124 125 126 127 128 129
#define gtk_box_new(ori, spacing)                                       \
  ((ori) == GTK_ORIENTATION_HORIZONTAL                                  \
   ? gtk_hbox_new (FALSE, (spacing)) : gtk_vbox_new (FALSE, (spacing)))
#define gtk_scrollbar_new(ori, spacing)                                 \
  ((ori) == GTK_ORIENTATION_HORIZONTAL                                  \
   ? gtk_hscrollbar_new ((spacing)) : gtk_vscrollbar_new ((spacing)))
130
#ifndef GDK_KEY_g
131 132
#define GDK_KEY_g GDK_g
#endif
133
#endif /* HAVE_GTK3 */
134

135 136
#define XG_BIN_CHILD(x) gtk_bin_get_child (GTK_BIN (x))

137
static void update_theme_scrollbar_width (void);
138
static void update_theme_scrollbar_height (void);
139

140 141 142 143 144 145 146 147 148 149
#define TB_INFO_KEY "xg_frame_tb_info"
struct xg_frame_tb_info
{
  Lisp_Object last_tool_bar;
  Lisp_Object style;
  int n_last_items;
  int hmargin, vmargin;
  GtkTextDirection dir;
};

150 151
static GtkWidget * xg_get_widget_from_map (ptrdiff_t idx);

152 153 154 155 156

/***********************************************************************
                      Display handling functions
 ***********************************************************************/

157 158
/* Keep track of the default display, or NULL if there is none.  Emacs
   may close all its displays.  */
Jan Djärv's avatar
Jan Djärv committed
159 160 161

static GdkDisplay *gdpy_def;

162 163 164
/* When the GTK widget W is to be created on a display for F that
   is not the default display, set the display for W.
   W can be a GtkMenu or a GtkWindow widget.  */
165

166
static void
Dmitry Antipov's avatar
Dmitry Antipov committed
167
xg_set_screen (GtkWidget *w, struct frame *f)
168
{
169
  if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ())
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
    {
      GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
      GdkScreen *gscreen = gdk_display_get_default_screen (gdpy);

      if (GTK_IS_MENU (w))
        gtk_menu_set_screen (GTK_MENU (w), gscreen);
      else
        gtk_window_set_screen (GTK_WINDOW (w), gscreen);
    }
}


/* Open a display named by DISPLAY_NAME.  The display is returned in *DPY.
   *DPY is set to NULL if the display can't be opened.

   Returns non-zero if display could be opened, zero if display could not
   be opened, and less than zero if the GTK version doesn't support
Paul Eggert's avatar
Paul Eggert committed
187
   multiple displays.  */
188

189
void
190
xg_display_open (char *display_name, Display **dpy)
191 192 193
{
  GdkDisplay *gdpy;

194
  unrequest_sigio ();  /* See comment in x_display_ok, xterm.c.  */
195
  gdpy = gdk_display_open (display_name);
Jan D's avatar
Jan D committed
196
  request_sigio ();
197 198 199 200 201 202
  if (!gdpy_def && gdpy)
    {
      gdpy_def = gdpy;
      gdk_display_manager_set_default_display (gdk_display_manager_get (),
					       gdpy);
    }
203

204
  *dpy = gdpy ? GDK_DISPLAY_XDISPLAY (gdpy) : NULL;
205 206
}

Lars Ingebrigtsen's avatar
Lars Ingebrigtsen committed
207
/* Scaling/HiDPI functions. */
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
static int
xg_get_gdk_scale (void)
{
  const char *sscale = getenv ("GDK_SCALE");

  if (sscale)
    {
      long scale = atol (sscale);
      if (0 < scale)
	return min (scale, INT_MAX);
    }

  return 1;
}

223 224 225
int
xg_get_scale (struct frame *f)
{
226
#if GTK_CHECK_VERSION (3, 10, 0)
227
  if (FRAME_GTK_WIDGET (f))
228
    return gtk_widget_get_scale_factor (FRAME_GTK_WIDGET (f));
229 230
#endif
  return xg_get_gdk_scale ();
231
}
232

233 234
/* Close display DPY.  */

235 236 237 238 239
void
xg_display_close (Display *dpy)
{
  GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);

240 241 242
  /* If this is the default display, try to change it before closing.
     If there is no other display to use, gdpy_def is set to NULL, and
     the next call to xg_display_open resets the default display.  */
243 244 245
  if (gdk_display_get_default () == gdpy)
    {
      struct x_display_info *dpyinfo;
246
      GdkDisplay *gdpy_new = NULL;
247 248 249 250 251

      /* Find another display.  */
      for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
        if (dpyinfo->display != dpy)
          {
252 253 254
	    gdpy_new = gdk_x11_lookup_xdisplay (dpyinfo->display);
	    gdk_display_manager_set_default_display (gdk_display_manager_get (),
						     gdpy_new);
255 256
            break;
          }
257
      gdpy_def = gdpy_new;
258 259
    }

260
#if GTK_CHECK_VERSION (2, 0, 0) && ! GTK_CHECK_VERSION (2, 10, 0)
261
  /* GTK 2.2-2.8 has a bug that makes gdk_display_close crash (bug
Glenn Morris's avatar
Glenn Morris committed
262
     https://gitlab.gnome.org/GNOME/gtk/issues/221).  This way we
263
     can continue running, but there will be memory leaks.  */
264
  g_object_run_dispose (G_OBJECT (gdpy));
265
#else
266
  /* This seems to be fixed in GTK 2.10. */
267 268 269
  gdk_display_close (gdpy);
#endif
}
Jan Djärv's avatar
Jan Djärv committed
270

Jan Djärv's avatar
Jan Djärv committed
271 272 273 274 275

/***********************************************************************
                      Utility functions
 ***********************************************************************/

276 277
/* Create and return the cursor to be used for popup menus and
   scroll bars on display DPY.  */
278

279
GdkCursor *
280
xg_create_default_cursor (Display *dpy)
281 282 283 284 285
{
  GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);
  return gdk_cursor_new_for_display (gdpy, GDK_LEFT_PTR);
}

286 287
/* Apply GMASK to GPIX and return a GdkPixbuf with an alpha channel.  */

288
static GdkPixbuf *
Dmitry Antipov's avatar
Dmitry Antipov committed
289
xg_get_pixbuf_from_pix_and_mask (struct frame *f,
290 291
                                 Pixmap pix,
                                 Pixmap mask)
292
{
293 294 295 296
  GdkPixbuf *icon_buf = 0;
  int iunused;
  Window wunused;
  unsigned int width, height, depth, uunused;
297

298 299 300 301 302 303 304 305 306
  if (FRAME_DISPLAY_INFO (f)->red_bits != 8)
    return 0;
  XGetGeometry (FRAME_X_DISPLAY (f), pix, &wunused, &iunused, &iunused,
                &width, &height, &uunused, &depth);
  if (depth != 24)
    return 0;
  XImage *xim = XGetImage (FRAME_X_DISPLAY (f), pix, 0, 0, width, height,
			   ~0, XYPixmap);
  if (xim)
307
    {
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
      XImage *xmm = (! mask ? 0
		     : XGetImage (FRAME_X_DISPLAY (f), mask, 0, 0,
				  width, height, ~0, XYPixmap));
      icon_buf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
      if (icon_buf)
	{
	  guchar *pixels = gdk_pixbuf_get_pixels (icon_buf);
	  int rowjunkwidth = gdk_pixbuf_get_rowstride (icon_buf) - width * 4;
	  for (int y = 0; y < height; y++, pixels += rowjunkwidth)
	    for (int x = 0; x < width; x++)
	      {
		unsigned long rgb = XGetPixel (xim, x, y);
		*pixels++ = (rgb >> 16) & 255;
		*pixels++ = (rgb >> 8) & 255;
		*pixels++ = rgb & 255;
		*pixels++ = xmm && !XGetPixel (xmm, x, y) ? 0 : 255;
	      }
	}

      if (xmm)
	XDestroyImage (xmm);
      XDestroyImage (xim);
330 331 332 333 334
    }

  return icon_buf;
}

335
static Lisp_Object
336
file_for_image (Lisp_Object image)
337 338 339 340 341 342 343 344 345 346 347 348 349
{
  Lisp_Object specified_file = Qnil;
  Lisp_Object tail;

  for (tail = XCDR (image);
       NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail));
       tail = XCDR (XCDR (tail)))
    if (EQ (XCAR (tail), QCfile))
      specified_file = XCAR (XCDR (tail));

  return specified_file;
}

350 351
/* For the image defined in IMG, make and return a GtkImage.  For displays with
   8 planes or less we must make a GdkPixbuf and apply the mask manually.
Paul Eggert's avatar
Paul Eggert committed
352
   Otherwise the highlighting and dimming the tool bar code in GTK does
353 354 355 356 357 358 359
   will look bad.  For display with more than 8 planes we just use the
   pixmap and mask directly.  For monochrome displays, GTK doesn't seem
   able to use external pixmaps, it looks bad whatever we do.
   The image is defined on the display where frame F is.
   WIDGET is used to find the GdkColormap to use for the GdkPixbuf.
   If OLD_WIDGET is NULL, a new widget is constructed and returned.
   If OLD_WIDGET is not NULL, that widget is modified.  */
360

361
static GtkWidget *
Dmitry Antipov's avatar
Dmitry Antipov committed
362
xg_get_image_for_pixmap (struct frame *f,
363 364 365
                         struct image *img,
                         GtkWidget *widget,
                         GtkImage *old_widget)
366
{
367
  GdkPixbuf *icon_buf;
368

369
  /* If we have a file, let GTK do all the image handling.
370
     This seems to be the only way to make insensitive and activated icons
371
     look good in all cases.  */
372
  Lisp_Object specified_file = file_for_image (img->spec);
373
  Lisp_Object file;
374

375 376 377
  /* We already loaded the image once before calling this
     function, so this only fails if the image file has been removed.
     In that case, use the pixmap already loaded.  */
378

379 380 381
  if (STRINGP (specified_file)
      && STRINGP (file = x_find_image_file (specified_file)))
    {
382
      char *encoded_file = SSDATA (ENCODE_FILE (file));
383
      if (! old_widget)
384
        old_widget = GTK_IMAGE (gtk_image_new_from_file (encoded_file));
385
      else
386
        gtk_image_set_from_file (old_widget, encoded_file);
387 388

      return GTK_WIDGET (old_widget);
389
    }
390

391 392 393 394
  /* No file, do the image handling ourselves.  This will look very bad
     on a monochrome display, and sometimes bad on all displays with
     certain themes.  */

395 396 397 398 399 400 401 402 403
  /* This is a workaround to make icons look good on pseudo color
     displays.  Apparently GTK expects the images to have an alpha
     channel.  If they don't, insensitive and activated icons will
     look bad.  This workaround does not work on monochrome displays,
     and is strictly not needed on true color/static color displays (i.e.
     16 bits and higher).  But we do it anyway so we get a pixbuf that is
     not associated with the img->pixmap.  The img->pixmap may be removed
     by clearing the image cache and then the tool bar redraw fails, since
     Gtk+ assumes the pixmap is always there.  */
404
  icon_buf = xg_get_pixbuf_from_pix_and_mask (f, img->pixmap, img->mask);
405

406
  if (icon_buf)
407 408 409 410 411
    {
      if (! old_widget)
        old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf));
      else
        gtk_image_set_from_pixbuf (old_widget, icon_buf);
412

413 414
      g_object_unref (G_OBJECT (icon_buf));
    }
415

416
  return GTK_WIDGET (old_widget);
417 418 419 420 421 422
}


/* Set CURSOR on W and all widgets W contain.  We must do like this
   for scroll bars and menu because they create widgets internally,
   and it is those widgets that are visible.  */
423

424
static void
425
xg_set_cursor (GtkWidget *w, GdkCursor *cursor)
Jan Djärv's avatar
Jan Djärv committed
426
{
Juanma Barranquero's avatar
Juanma Barranquero committed
427
  GdkWindow *window = gtk_widget_get_window (w);
428
  GList *children = gdk_window_peek_children (window);
Jan Djärv's avatar
Jan Djärv committed
429

430
  gdk_window_set_cursor (window, cursor);
Jan Djärv's avatar
Jan Djärv committed
431 432 433 434 435 436 437

  /* The scroll bar widget has more than one GDK window (had to look at
     the source to figure this out), and there is no way to set cursor
     on widgets in GTK.  So we must set the cursor for all GDK windows.
     Ditto for menus.  */

  for ( ; children; children = g_list_next (children))
438
    gdk_window_set_cursor (GDK_WINDOW (children->data), cursor);
Jan Djärv's avatar
Jan Djärv committed
439 440 441
}

/* Insert NODE into linked LIST.  */
442

Jan Djärv's avatar
Jan Djärv committed
443 444 445 446
static void
xg_list_insert (xg_list_node *list, xg_list_node *node)
{
  xg_list_node *list_start = list->next;
447

Jan Djärv's avatar
Jan Djärv committed
448 449 450 451 452 453 454
  if (list_start) list_start->prev = node;
  node->next = list_start;
  node->prev = 0;
  list->next = node;
}

/* Remove NODE from linked LIST.  */
455

Jan Djärv's avatar
Jan Djärv committed
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
static void
xg_list_remove (xg_list_node *list, xg_list_node *node)
{
  xg_list_node *list_start = list->next;
  if (node == list_start)
    {
      list->next = node->next;
      if (list->next) list->next->prev = 0;
    }
  else
    {
      node->prev->next = node->next;
      if (node->next) node->next->prev = node->prev;
    }
}

/* Allocate and return a utf8 version of STR.  If STR is already
473 474
   utf8 or NULL, just return a copy of STR.
   A new string is allocated and the caller must free the result
Jan Djärv's avatar
Jan Djärv committed
475
   with g_free.  */
476

Jan Djärv's avatar
Jan Djärv committed
477
static char *
478
get_utf8_string (const char *str)
Jan Djärv's avatar
Jan Djärv committed
479
{
480
  char *utf8_str;
481

482 483
  if (!str) return NULL;

Jan Djärv's avatar
Jan Djärv committed
484
  /* If not UTF-8, try current locale.  */
485
  if (!g_utf8_validate (str, -1, NULL))
Jan Djärv's avatar
Jan Djärv committed
486
    utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0);
487 488
  else
    return g_strdup (str);
Jan Djärv's avatar
Jan Djärv committed
489

Juanma Barranquero's avatar
Juanma Barranquero committed
490
  if (!utf8_str)
491 492
    {
      /* Probably some control characters in str.  Escape them. */
493 494
      ptrdiff_t len;
      ptrdiff_t nr_bad = 0;
495 496 497 498
      gsize bytes_read;
      gsize bytes_written;
      unsigned char *p = (unsigned char *)str;
      char *cp, *up;
499
      GError *err = NULL;
500

501
      while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
502 503
                                       &bytes_written, &err))
             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
504 505 506
        {
          ++nr_bad;
          p += bytes_written+1;
507 508
          g_error_free (err);
          err = NULL;
509 510
        }

511
      if (err)
512
        {
513 514
          g_error_free (err);
          err = NULL;
515 516 517
        }
      if (cp) g_free (cp);

518
      len = strlen (str);
519 520 521 522
      ptrdiff_t alloc;
      if (INT_MULTIPLY_WRAPV (nr_bad, 4, &alloc)
	  || INT_ADD_WRAPV (len + 1, alloc, &alloc)
	  || SIZE_MAX < alloc)
523
	memory_full (SIZE_MAX);
524
      up = utf8_str = xmalloc (alloc);
525
      p = (unsigned char *)str;
526

527
      while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
528 529
                                       &bytes_written, &err))
             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
530
        {
531
          memcpy (up, p, bytes_written);
532 533 534
          up += bytes_written;
          up += sprintf (up, "\\%03o", p[bytes_written]);
          p += bytes_written + 1;
535 536
          g_error_free (err);
          err = NULL;
537 538
        }

Juanma Barranquero's avatar
Juanma Barranquero committed
539
      if (cp)
540
        {
541
          strcpy (up, cp);
542 543
          g_free (cp);
        }
544
      if (err)
545
        {
546 547
          g_error_free (err);
          err = NULL;
548 549
        }
    }
Jan Djärv's avatar
Jan Djärv committed
550 551 552
  return utf8_str;
}

553 554
/* Check for special colors used in face spec for region face.
   The colors are fetched from the Gtk+ theme.
555
   Return true if color was found, false if not.  */
556

557
bool
558 559 560 561
xg_check_special_colors (struct frame *f,
                         const char *color_name,
                         XColor *color)
{
562 563 564
  bool success_p = 0;
  bool get_bg = strcmp ("gtk_selection_bg_color", color_name) == 0;
  bool get_fg = !get_bg && strcmp ("gtk_selection_fg_color", color_name) == 0;
565 566 567 568

  if (! FRAME_GTK_WIDGET (f) || ! (get_bg || get_fg))
    return success_p;

569
  block_input ();
570 571 572 573 574
  {
#ifdef HAVE_GTK3
    GtkStyleContext *gsty
      = gtk_widget_get_style_context (FRAME_GTK_OUTER_WIDGET (f));
    GdkRGBA col;
575
    char buf[sizeof "rgb://rrrr/gggg/bbbb"];
576 577 578 579
    int state = GTK_STATE_FLAG_SELECTED|GTK_STATE_FLAG_FOCUSED;
    if (get_fg)
      gtk_style_context_get_color (gsty, state, &col);
    else
580 581 582
      {
        GdkRGBA *c;
        /* FIXME: Retrieving the background color is deprecated in
583 584
           GTK+ 3.16.  New versions of GTK+ don't use the concept of a
           single background color any more, so we shouldn't query for
585 586 587 588 589 590 591
           it.  */
        gtk_style_context_get (gsty, state,
                               GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &c,
                               NULL);
        col = *c;
        gdk_rgba_free (c);
      }
592

593 594 595 596 597
    unsigned short
      r = col.red * 65535,
      g = col.green * 65535,
      b = col.blue * 65535;
    sprintf (buf, "rgb:%04x/%04x/%04x", r, g, b);
598
    success_p = x_parse_color (f, buf, color) != 0;
599 600 601 602 603 604 605 606 607 608 609 610
#else
    GtkStyle *gsty = gtk_widget_get_style (FRAME_GTK_WIDGET (f));
    GdkColor *grgb = get_bg
      ? &gsty->bg[GTK_STATE_SELECTED]
      : &gsty->fg[GTK_STATE_SELECTED];

    color->red = grgb->red;
    color->green = grgb->green;
    color->blue = grgb->blue;
    color->pixel = grgb->pixel;
    success_p = 1;
#endif
611

612
  }
613
  unblock_input ();
614 615 616
  return success_p;
}

Jan Djärv's avatar
Jan Djärv committed
617

618 619 620 621 622 623 624 625

/***********************************************************************
                              Tooltips
 ***********************************************************************/
/* Gtk+ calls this callback when the parent of our tooltip dummy changes.
   We use that to pop down the tooltip.  This happens if Gtk+ for some
   reason wants to change or hide the tooltip.  */

626 627
#ifdef USE_GTK_TOOLTIP

628 629 630 631 632
static void
hierarchy_ch_cb (GtkWidget *widget,
                 GtkWidget *previous_toplevel,
                 gpointer   user_data)
{
Paul Eggert's avatar
Paul Eggert committed
633
  struct frame *f = user_data;
634 635
  struct x_output *x = f->output_data.x;
  GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
Chong Yidong's avatar
Chong Yidong committed
636

637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
  if (! top || ! GTK_IS_WINDOW (top))
      gtk_widget_hide (previous_toplevel);
}

/* Callback called when Gtk+ thinks a tooltip should be displayed.
   We use it to get the tooltip window and the tooltip widget so
   we can manipulate the ourselves.

   Return FALSE ensures that the tooltip is not shown.  */

static gboolean
qttip_cb (GtkWidget  *widget,
          gint        xpos,
          gint        ypos,
          gboolean    keyboard_mode,
          GtkTooltip *tooltip,
          gpointer    user_data)
{
Paul Eggert's avatar
Paul Eggert committed
655
  struct frame *f = user_data;
656
  struct x_output *x = f->output_data.x;
Chong Yidong's avatar
Chong Yidong committed
657
  if (x->ttip_widget == NULL)
658
    {
659 660 661
      GtkWidget *p;
      GList *list, *iter;

662 663 664 665 666 667 668
      g_object_set (G_OBJECT (widget), "has-tooltip", FALSE, NULL);
      x->ttip_widget = tooltip;
      g_object_ref (G_OBJECT (tooltip));
      x->ttip_lbl = gtk_label_new ("");
      g_object_ref (G_OBJECT (x->ttip_lbl));
      gtk_tooltip_set_custom (tooltip, x->ttip_lbl);
      x->ttip_window = GTK_WINDOW (gtk_widget_get_toplevel (x->ttip_lbl));
669 670 671 672 673 674 675 676 677 678 679 680

      /* Change stupid Gtk+ default line wrapping.  */
      p = gtk_widget_get_parent (x->ttip_lbl);
      list = gtk_container_get_children (GTK_CONTAINER (p));
      for (iter = list; iter; iter = g_list_next (iter))
        {
          GtkWidget *w = GTK_WIDGET (iter->data);
          if (GTK_IS_LABEL (w))
            gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
        }
      g_list_free (list);

681 682
      /* ATK needs an empty title for some reason.  */
      gtk_window_set_title (x->ttip_window, "");
683 684 685 686 687 688 689 690 691 692
      /* Realize so we can safely get screen later on.  */
      gtk_widget_realize (GTK_WIDGET (x->ttip_window));
      gtk_widget_realize (x->ttip_lbl);

      g_signal_connect (x->ttip_lbl, "hierarchy-changed",
                        G_CALLBACK (hierarchy_ch_cb), f);
    }
  return FALSE;
}

693 694
#endif /* USE_GTK_TOOLTIP */

695
/* Prepare a tooltip to be shown, i.e. calculate WIDTH and HEIGHT.
696
   Return true if a system tooltip is available.  */
697

698
bool
Dmitry Antipov's avatar
Dmitry Antipov committed
699
xg_prepare_tooltip (struct frame *f,
Jan Djärv's avatar
Jan Djärv committed
700 701
                    Lisp_Object string,
                    int *width,
702 703
                    int *height)
{
704 705 706
#ifndef USE_GTK_TOOLTIP
  return 0;
#else
707 708 709 710 711 712 713 714 715 716 717
  struct x_output *x = f->output_data.x;
  GtkWidget *widget;
  GdkWindow *gwin;
  GdkScreen *screen;
  GtkSettings *settings;
  gboolean tt_enabled = TRUE;
  GtkRequisition req;
  Lisp_Object encoded_string;

  if (!x->ttip_lbl) return 0;

718
  block_input ();
719 720 721
  encoded_string = ENCODE_UTF_8 (string);
  widget = GTK_WIDGET (x->ttip_lbl);
  gwin = gtk_widget_get_window (GTK_WIDGET (x->ttip_window));
722
  screen = gdk_window_get_screen (gwin);
723 724
  settings = gtk_settings_get_for_screen (screen);
  g_object_get (settings, "gtk-enable-tooltips", &tt_enabled, NULL);
Chong Yidong's avatar
Chong Yidong committed
725
  if (tt_enabled)
726 727 728 729 730 731
    {
      g_object_set (settings, "gtk-enable-tooltips", FALSE, NULL);
      /* Record that we disabled it so it can be enabled again.  */
      g_object_set_data (G_OBJECT (x->ttip_window), "restore-tt",
                         (gpointer)f);
    }
Chong Yidong's avatar
Chong Yidong committed
732

733 734 735 736 737
  /* Prevent Gtk+ from hiding tooltip on mouse move and such.  */
  g_object_set_data (G_OBJECT
                     (gtk_widget_get_display (GTK_WIDGET (x->ttip_window))),
                     "gdk-display-current-tooltip", NULL);

Jan Djärv's avatar
Jan Djärv committed
738
  /* Put our dummy widget in so we can get callbacks for unrealize and
739 740
     hierarchy-changed.  */
  gtk_tooltip_set_custom (x->ttip_widget, widget);
741
  gtk_tooltip_set_text (x->ttip_widget, SSDATA (encoded_string));
742
  gtk_widget_get_preferred_size (GTK_WIDGET (x->ttip_window), NULL, &req);
743 744
  if (width) *width = req.width;
  if (height) *height = req.height;
Chong Yidong's avatar
Chong Yidong committed
745

746
  unblock_input ();
747 748

  return 1;
749
#endif /* USE_GTK_TOOLTIP */
750 751 752 753 754 755
}

/* Show the tooltip at ROOT_X and ROOT_Y.
   xg_prepare_tooltip must have been called before this function.  */

void
Dmitry Antipov's avatar
Dmitry Antipov committed
756
xg_show_tooltip (struct frame *f, int root_x, int root_y)
757
{
758
#ifdef USE_GTK_TOOLTIP
759 760 761
  struct x_output *x = f->output_data.x;
  if (x->ttip_window)
    {
762
      block_input ();
763 764
      gtk_window_move (x->ttip_window, root_x / xg_get_scale (f),
		       root_y / xg_get_scale (f));
765
      gtk_widget_show_all (GTK_WIDGET (x->ttip_window));
766
      unblock_input ();
767
    }
768
#endif
769 770 771
}

/* Hide tooltip if shown.  Do nothing if not shown.
772
   Return true if tip was hidden, false if not (i.e. not using
773 774
   system tooltips).  */

775
bool
Dmitry Antipov's avatar
Dmitry Antipov committed
776
xg_hide_tooltip (struct frame *f)
777
{
John Wiegley's avatar
John Wiegley committed
778
  bool ret = 0;
779
#ifdef USE_GTK_TOOLTIP
John Wiegley's avatar
John Wiegley committed
780
  if (f->output_data.x->ttip_window)
781 782
    {
      GtkWindow *win = f->output_data.x->ttip_window;
783
      block_input ();
784 785 786 787 788
      gtk_widget_hide (GTK_WIDGET (win));

      if (g_object_get_data (G_OBJECT (win), "restore-tt"))
        {
          GdkWindow *gwin = gtk_widget_get_window (GTK_WIDGET (win));
789
          GdkScreen *screen = gdk_window_get_screen (gwin);
790 791 792
          GtkSettings *settings = gtk_settings_get_for_screen (screen);
          g_object_set (settings, "gtk-enable-tooltips", TRUE, NULL);
        }
793
      unblock_input ();
Dmitry Antipov's avatar
Dmitry Antipov committed
794

John Wiegley's avatar
John Wiegley committed
795 796
      ret = 1;
    }
Dmitry Antipov's avatar
Dmitry Antipov committed
797
#endif
John Wiegley's avatar
John Wiegley committed
798
  return ret;
799 800
}

Jan Djärv's avatar
Jan Djärv committed
801 802 803 804 805

/***********************************************************************
    General functions for creating widgets, resizing, events, e.t.c.
 ***********************************************************************/

806
#if ! GTK_CHECK_VERSION (3, 22, 0)
807 808 809 810 811 812 813
static void
my_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
		const gchar *msg, gpointer user_data)
{
  if (!strstr (msg, "visible children"))
    fprintf (stderr, "XX %s-WARNING **: %s\n", log_domain, msg);
}
814
#endif
815

Jan Djärv's avatar
Jan Djärv committed
816 817 818 819
/* Make a geometry string and pass that to GTK.  It seems this is the
   only way to get geometry position right if the user explicitly
   asked for a position when starting Emacs.
   F is the frame we shall set geometry for.  */
820

Jan Djärv's avatar
Jan Djärv committed
821
static void
Dmitry Antipov's avatar
Dmitry Antipov committed
822
xg_set_geometry (struct frame *f)
Jan Djärv's avatar
Jan Djärv committed
823
{
824
  if (f->size_hint_flags & (USPosition | PPosition))
825
    {
826
      int scale = xg_get_scale (f);
827
#if ! GTK_CHECK_VERSION (3, 22, 0)
828 829
      if (x_gtk_use_window_move)
	{
830
#endif
831 832 833 834 835 836 837 838 839 840 841
	  /* Handle negative positions without consulting
	     gtk_window_parse_geometry (Bug#25851).  The position will
	     be off by scrollbar width + window manager decorations.  */
	  if (f->size_hint_flags & XNegative)
	    f->left_pos = (x_display_pixel_width (FRAME_DISPLAY_INFO (f))
			   - FRAME_PIXEL_WIDTH (f) + f->left_pos);

	  if (f->size_hint_flags & YNegative)
	    f->top_pos = (x_display_pixel_height (FRAME_DISPLAY_INFO (f))
			  - FRAME_PIXEL_HEIGHT (f) + f->top_pos);

842
	  /* GTK works in scaled pixels, so convert from X pixels.  */
843
	  gtk_window_move (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
844
			   f->left_pos / scale, f->top_pos / scale);
845 846 847

	  /* Reset size hint flags.  */
	  f->size_hint_flags &= ~ (XNegative | YNegative);
848
# if ! GTK_CHECK_VERSION (3, 22, 0)
849 850 851
	}
      else
	{
852 853
          /* GTK works in scaled pixels, so convert from X pixels.  */
	  int left = f->left_pos / scale;
854
	  int xneg = f->size_hint_flags & XNegative;
855
	  int top = f->top_pos / scale;
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
	  int yneg = f->size_hint_flags & YNegative;
	  char geom_str[sizeof "=x--" + 4 * INT_STRLEN_BOUND (int)];
	  guint id;

	  if (xneg)
	    left = -left;
	  if (yneg)
	    top = -top;

	  sprintf (geom_str, "=%dx%d%c%d%c%d",
		   FRAME_PIXEL_WIDTH (f),
		   FRAME_PIXEL_HEIGHT (f),
		   (xneg ? '-' : '+'), left,
		   (yneg ? '-' : '+'), top);

	  /* Silence warning about visible children.  */
	  id = g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL
				  | G_LOG_FLAG_RECURSION, my_log_handler, NULL);

	  if (!gtk_window_parse_geometry (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
					  geom_str))
	    fprintf (stderr, "Failed to parse: '%s'\n", geom_str);

	  g_log_remove_handler ("Gtk", id);
	}
881
#endif
882
    }
Jan Djärv's avatar
Jan Djärv committed
883 884
}

885 886 887
/* Function to handle resize of our frame.  As we have a Gtk+ tool bar
   and a Gtk+ menu bar, we get resize events for the edit part of the
   frame only.  We let Gtk+ deal with the Gtk+ parts.
Jan Djärv's avatar
Jan Djärv committed
888 889
   F is the frame to resize.
   PIXELWIDTH, PIXELHEIGHT is the new size in pixels.  */
890

Jan Djärv's avatar
Jan Djärv committed
891
void
Dmitry Antipov's avatar
Dmitry Antipov committed
892
xg_frame_resized (struct frame *f, int pixelwidth, int pixelheight)
Jan Djärv's avatar
Jan Djärv committed
893
{
894
  int width, height;
895 896 897

  if (pixelwidth == -1 && pixelheight == -1)
    {
898
      if (FRAME_GTK_WIDGET (f) && gtk_widget_get_mapped (FRAME_GTK_WIDGET (f)))
899 900 901 902
	gdk_window_get_geometry (gtk_widget_get_window (FRAME_GTK_WIDGET (f)),
				 0, 0, &pixelwidth, &pixelheight);
      else
	return;
903
    }
Chong Yidong's avatar
Chong Yidong committed
904

905 906
  width = FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth);
  height = FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight);
907

908 909 910
  frame_size_history_add
    (f, Qxg_frame_resized, width, height, Qnil);

911 912
  if (width != FRAME_TEXT_WIDTH (f)
      || height != FRAME_TEXT_HEIGHT (f)
913 914
      || pixelwidth != FRAME_PIXEL_WIDTH (f)
      || pixelheight != FRAME_PIXEL_HEIGHT (f))
Jan Djärv's avatar
Jan Djärv committed
915
    {
916
      x_clear_under_internal_border (f);
917
      change_frame_size (f, width, height, 0, 1, 0, 1);
918 919
      SET_FRAME_GARBAGED (f);
      cancel_mouse_face (f);
920 921
    }
}
Jan Djärv's avatar
Jan Djärv committed
922

Paul Eggert's avatar
Paul Eggert committed
923
/* Resize the outer window of frame F after changing the height.
924
   COLUMNS/ROWS is the size the edit area shall have after the resize.  */
925

Jan Djärv's avatar
Jan Djärv committed
926
void
927
xg_frame_set_char_size (struct frame *f, int width, int height)
Jan Djärv's avatar
Jan Djärv committed
928
{
929
  int pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width);
930
  int pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height);
931 932
  Lisp_Object fullscreen = get_frame_param (f, Qfullscreen);
  gint gwidth, gheight;
933 934
  int totalheight
    = pixelheight + FRAME_TOOLBAR_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f);
Jan D's avatar
Jan D committed
935
  int totalwidth = pixelwidth + FRAME_TOOLBAR_WIDTH (f);
936

937 938 939
  if (FRAME_PIXEL_HEIGHT (f) == 0)
    return;

940 941 942
  gtk_window_get_size (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
		       &gwidth, &gheight);

943
  /* Do this before resize, as we don't know yet if we will be resized.  */
944
  x_clear_under_internal_border (f);
945

946 947
  totalheight /= xg_get_scale (f);
  totalwidth /= xg_get_scale (f);
Jan D's avatar
Jan D committed
948

949 950
  x_wm_set_size_hint (f, 0, 0);

951 952 953 954 955 956 957 958 959 960 961 962 963
  /* Resize the top level widget so rows and columns remain constant.

     When the frame is fullheight and we only want to change the width
     or it is fullwidth and we only want to change the height we should
     be able to preserve the fullscreen property.  However, due to the
     fact that we have to send a resize request anyway, the window
     manager will abolish it.  At least the respective size should
     remain unchanged but giving the frame back its normal size will
     be broken ... */
  if (EQ (fullscreen, Qfullwidth) && width == FRAME_TEXT_WIDTH (f))
    {
      frame_size_history_add
	(f, Qxg_frame_set_char_size_1, width, height,
964
	 list2 (make_number (gheight), make_number (totalheight)));
965 966

      gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
967
			 gwidth, totalheight);
968 969 970 971 972
    }
  else if (EQ (fullscreen, Qfullheight) && height == FRAME_TEXT_HEIGHT (f))
    {
      frame_size_history_add
	(f, Qxg_frame_set_char_size_2, width, height,
973
	 list2 (make_number (gwidth), make_number (totalwidth)));
974 975

      gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
976
			 totalwidth, gheight);
977 978 979 980 981
    }
  else
    {
      frame_size_history_add
	(f, Qxg_frame_set_char_size_3, width, height,
982
	 list2 (make_number (totalwidth), make_number (totalheight)));
983 984

      gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
985
			 totalwidth, totalheight);
986 987
      fullscreen = Qnil;
    }
988

989 990 991
  SET_FRAME_GARBAGED (f);
  cancel_mouse_face (f);

992 993 994 995