gucharmap-chartable.c 80.7 KB
Newer Older
1
/*
Christian Persch's avatar
Christian Persch committed
2
 * Copyright © 2004 Noah Levitt
3
 * Copyright © 2007, 2008, 2010 Christian Persch
4
 *
5
 * Some code copied from gtk+/gtk/gtkiconview:
Christian Persch's avatar
Christian Persch committed
6
 * Copyright © 2002, 2004  Anders Carlsson <andersca@gnu.org>
7
 *
8 9
 * 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
Christian Persch's avatar
Christian Persch committed
10
 * Free Software Foundation; either version 3 of the License, or (at your
11 12 13 14 15 16 17 18 19
 * 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.,
20
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
21 22
 */

Christian Persch's avatar
Christian Persch committed
23
#include <config.h>
24

25
#include <glib/gi18n-lib.h>
26
#include <gtk/gtk.h>
27

28 29 30
#include "gucharmap-marshal.h"
#include "gucharmap-chartable.h"
#include "gucharmap-unicode-info.h"
31
#include "gucharmap-private.h"
32

33
#define ENABLE_ACCESSIBLE
34 35

#ifdef ENABLE_ACCESSIBLE
36
#include "gucharmap-chartable-accessible.h"
37 38
#endif

39
enum
40 41 42
{
  ACTIVATE,
  STATUS_MESSAGE,
43
  MOVE_CURSOR,
44 45
  COPY_CLIPBOARD,
  PASTE_CLIPBOARD,
46 47 48
  NUM_SIGNALS
};

49 50 51
enum
{
  PROP_0,
52 53
  PROP_HADJUSTMENT,
  PROP_VADJUSTMENT,
54 55
  PROP_HSCROLL_POLICY,
  PROP_VSCROLL_POLICY,
56
  PROP_ACTIVE_CHAR,
57
  PROP_CODEPOINT_LIST,
58
  PROP_FONT_DESC,
59
  PROP_FONT_FALLBACK,
60
  PROP_SNAP_POW2,
61 62
  PROP_ZOOM_ENABLED,
  PROP_ZOOM_SHOWING
63 64
};

65 66
static void gucharmap_chartable_class_init (GucharmapChartableClass *klass);
static void gucharmap_chartable_finalize   (GObject *object);
Christian Persch's avatar
Christian Persch committed
67 68
static void gucharmap_chartable_set_active_cell (GucharmapChartable *chartable,
                                                 int cell);
69

70
static guint signals[NUM_SIGNALS];
71

72 73 74 75 76 77 78 79 80
#define DEFAULT_FONT_SIZE (20.0 * (double) PANGO_SCALE)

/* These are chosen for compatibility with the older code that
 * didn't scale the font size by resolution and used 3 and 2.5 here, resp.
 * Where exactly these factors came from, I don't know.
 */
#define FACTOR_WIDTH (2.25) /* 3 / (96 / 72) */
#define FACTOR_HEIGHT (1.875) /* 2.5 / (96 / 72) */

81 82 83 84 85 86 87 88 89 90 91
/** Notes
 *
 * 1. Table geometry
 * The allocated rectangle is divided into ::rows rows and ::col columns,
 * numbered 0..rows-1 and 0..cols-1.
 * The available width (height) is divided evenly between all columns (rows).
 * The remaining space is distributed among the columns (rows) so that
 * columns cols-n_padded_columns .. cols-1 (rows rows-n_padded_rows .. rows)
 * are 1px wider (taller) than the others.
 */

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
/* ATK factory */

#ifdef ENABLE_ACCESSIBLE

typedef AtkObjectFactory      GucharmapChartableAccessibleFactory;
typedef AtkObjectFactoryClass GucharmapChartableAccessibleFactoryClass;

static void
gucharmap_chartable_accessible_factory_init (GucharmapChartableAccessibleFactory *factory)
{
}

static AtkObject*
gucharmap_chartable_accessible_factory_create_accessible (GObject *obj)
{
  return gucharmap_chartable_accessible_new (GUCHARMAP_CHARTABLE (obj));
}

static GType
gucharmap_chartable_accessible_factory_get_accessible_type (void)
{
  return gucharmap_chartable_accessible_get_type ();
}

static void
gucharmap_chartable_accessible_factory_class_init (AtkObjectFactoryClass *klass)
{
  klass->create_accessible = gucharmap_chartable_accessible_factory_create_accessible;
  klass->get_accessible_type = gucharmap_chartable_accessible_factory_get_accessible_type;
}

static GType gucharmap_chartable_accessible_factory_get_type (void);
G_DEFINE_TYPE (GucharmapChartableAccessibleFactory, gucharmap_chartable_accessible_factory, ATK_TYPE_OBJECT_FACTORY)

#endif

/* Type definition */

130 131
G_DEFINE_TYPE_WITH_CODE (GucharmapChartable, gucharmap_chartable, GTK_TYPE_DRAWING_AREA,
                         G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
132

133
/* utility functions */
Christian Persch's avatar
Christian Persch committed
134

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
static void
gucharmap_chartable_clear_pango_layout (GucharmapChartable *chartable)
{
  GucharmapChartablePrivate *priv = chartable->priv;

  if (priv->pango_layout == NULL)
    return;
  g_object_unref (priv->pango_layout);
  priv->pango_layout = NULL;
}

static void
gucharmap_chartable_ensure_pango_layout (GucharmapChartable *chartable)
{
  GucharmapChartablePrivate *priv = chartable->priv;

  if (priv->pango_layout != NULL)
    return;

  priv->pango_layout = gtk_widget_create_pango_layout (GTK_WIDGET (chartable), NULL);
  pango_layout_set_font_description (priv->pango_layout,
                                     priv->font_desc);

  if (priv->font_fallback == FALSE) 
    {
      PangoAttrList *list;

      list = pango_attr_list_new ();
      pango_attr_list_insert (list, pango_attr_fallback_new (FALSE));
      pango_layout_set_attributes (priv->pango_layout, list);
      pango_attr_list_unref (list);
    }
}

169
static void
170 171
gucharmap_chartable_set_font_desc_internal (GucharmapChartable *chartable,
                                            PangoFontDescription *font_desc /* adopting */)
172
{
173
  GucharmapChartablePrivate *priv = chartable->priv;
174
  GtkWidget *widget;
175

176 177
  if (priv->font_desc)
    pango_font_description_free (priv->font_desc);
178

179
  priv->font_desc = font_desc;
180

181 182 183 184 185
  gucharmap_chartable_clear_pango_layout (chartable);

  widget = GTK_WIDGET (chartable);
  if (gtk_widget_get_realized (widget))
    gtk_widget_queue_resize (widget);
186 187

  g_object_notify (G_OBJECT (chartable), "font-desc");
188 189
}

190
static void
Christian Persch's avatar
Christian Persch committed
191 192
gucharmap_chartable_emit_status_message (GucharmapChartable *chartable,
                                         const char *message)
193 194 195 196
{
  g_signal_emit (chartable, signals[STATUS_MESSAGE], 0, message);
}

197 198 199 200 201 202 203 204 205 206 207
typedef enum {
  POSITION_DOWN_ALIGN_LEFT,
  POSITION_DOWN_ALIGN_RIGHT,
  POSITION_RIGHT_ALIGN_TOP,
  POSITION_RIGHT_ALIGN_BOTTOM,
  POSITION_TOP_ALIGN_LEFT,
  POSITION_TOP_ALIGN_RIGHT,
  POSITION_LEFT_ALIGN_TOP,
  POSITION_LEFT_ALIGN_BOTTOM
} PositionType;

208 209 210 211 212 213 214 215 216 217 218
static const PositionType rtl_position[] = {
  POSITION_DOWN_ALIGN_RIGHT,
  POSITION_DOWN_ALIGN_LEFT,
  POSITION_LEFT_ALIGN_TOP,
  POSITION_LEFT_ALIGN_BOTTOM,
  POSITION_TOP_ALIGN_RIGHT,
  POSITION_TOP_ALIGN_LEFT,
  POSITION_RIGHT_ALIGN_TOP,
  POSITION_RIGHT_ALIGN_BOTTOM
};

219 220
/**
 * position_rectangle:
221 222
 * @rect: the rectangle to position. Inout; width and height must be initialised
 * @target_rect: the rectangle to position @rect on
223
 * @bounding_rect: the bounding rectangle
224 225
 * @position: how to position the rectangle
 * @direction: the text direction
226 227 228 229 230 231
 *
 * Returns: %TRUE if @rect could be positioned on @reference_point
 * with positioning according to @gravity inside @bounding_rect
 */ 
static gboolean
position_rectangle (GdkRectangle *position_rect,
232
                    GdkRectangle *target_rect,
233 234 235
                    GdkRectangle *bounding_rect,
                    PositionType position,
                    GtkTextDirection direction)
236 237
{
  GdkRectangle rect;
238 239 240 241

  if (direction == GTK_TEXT_DIR_RTL) {
    position = rtl_position[position];
  }
242

243 244
  rect.x = target_rect->x;
  rect.y = target_rect->y;
245 246 247
  rect.width = position_rect->width;
  rect.height = position_rect->height;

248 249 250 251 252 253
  switch (position) {
    case POSITION_DOWN_ALIGN_RIGHT:
      rect.x -= rect.width - target_rect->width;
      /* fall-through */
    case POSITION_DOWN_ALIGN_LEFT:
      rect.y += target_rect->height;
254 255
      break;

256
    case POSITION_RIGHT_ALIGN_BOTTOM:
257
      rect.y -= rect.height - target_rect->height;
258 259 260
      /* fall-through */
    case POSITION_RIGHT_ALIGN_TOP:
      rect.x += target_rect->width;
261 262
      break;

263 264 265 266 267
    case POSITION_TOP_ALIGN_RIGHT:
      rect.x -= rect.width - target_rect->width;
      /* fall-through */
    case POSITION_TOP_ALIGN_LEFT:
      rect.y -= rect.height;
268 269
      break;

270 271 272 273 274
    case POSITION_LEFT_ALIGN_BOTTOM:
      rect.y -= rect.height - target_rect->height;
      /* fall-through */
    case POSITION_LEFT_ALIGN_TOP:
      rect.x -= rect.width;
275 276 277 278 279 280 281 282 283 284 285 286 287 288
      break;
  }

  *position_rect = rect;

  return rect.x >= bounding_rect->x &&
         rect.y >= bounding_rect->y &&
         rect.x + rect.width <= bounding_rect->x + bounding_rect->width &&
         rect.y + rect.height <= bounding_rect->y + bounding_rect->height;
}

static gboolean
position_rectangle_on_screen (GtkWidget *widget,
                              GdkRectangle *rectangle,
289
                              GdkRectangle *target_rect)
290 291 292 293 294
{
  GtkTextDirection direction;
  GdkRectangle monitor;
  int monitor_num;
  GdkScreen *screen;
295
  static const PositionType positions[] = {
296 297 298 299 300 301 302 303 304 305
    POSITION_DOWN_ALIGN_LEFT,
    POSITION_TOP_ALIGN_LEFT,
    POSITION_RIGHT_ALIGN_TOP,
    POSITION_LEFT_ALIGN_TOP,
    POSITION_DOWN_ALIGN_RIGHT,
    POSITION_TOP_ALIGN_RIGHT,
    POSITION_RIGHT_ALIGN_BOTTOM,
    POSITION_LEFT_ALIGN_BOTTOM
  };
  guint i;
306 307 308
  
  direction = gtk_widget_get_direction (widget);
  screen = gtk_widget_get_screen (widget);
309
  monitor_num = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (widget));
310 311
  if (monitor_num < 0)
    monitor_num = 0;
312 313 314
#if GTK_CHECK_VERSION (3, 3, 5)
  gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor);
#else
315
  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
316
#endif
317

318
  for (i = 0; i < G_N_ELEMENTS (positions); ++i) {
319
    if (position_rectangle (rectangle, target_rect, &monitor, positions[i], direction))
320 321
      return TRUE;
  }
322 323 324

  return FALSE;
}
325 326 327 328 329 330

static void
get_root_coords_at_active_char (GucharmapChartable *chartable, 
                                gint *x_root, 
                                gint *y_root)
{
331
  GucharmapChartablePrivate *priv = chartable->priv;
332 333 334 335
  GtkWidget *widget = GTK_WIDGET (chartable);
  gint x, y;
  gint row, col;

336
  gdk_window_get_origin (gtk_widget_get_window (widget), &x, &y);
337

338 339
  row = (priv->active_cell - priv->page_first_cell) / priv->cols;
  col = _gucharmap_chartable_cell_column (chartable, priv->active_cell);
340 341 342 343 344 345

  *x_root = x + _gucharmap_chartable_x_offset (chartable, col);
  *y_root = y + _gucharmap_chartable_y_offset (chartable, row);
}

static void
346
get_active_cell_rect (GucharmapChartable *chartable, GdkRectangle *rect)
347
{
348
  GucharmapChartablePrivate *priv = chartable->priv;
349
  int row, col;
350

351
  get_root_coords_at_active_char (chartable, &rect->x, &rect->y);
352

353 354
  row = (priv->active_cell - priv->page_first_cell) / priv->cols;
  col = _gucharmap_chartable_cell_column (chartable, priv->active_cell);
355

356 357
  rect->width = _gucharmap_chartable_column_width (chartable, col);
  rect->height = _gucharmap_chartable_row_height (chartable, row);
358 359 360 361 362 363 364 365
}

static void
get_appropriate_upper_left_xy (GucharmapChartable *chartable, 
                               gint width,  gint height,
                               gint x_root, gint y_root,
                               gint *x,     gint *y)
{
366
  GucharmapChartablePrivate *priv = chartable->priv;
367 368
  gint row, col;

369 370
  row = (priv->active_cell - priv->page_first_cell) / priv->cols;
  col = _gucharmap_chartable_cell_column (chartable, priv->active_cell);
371 372 373 374

  *x = x_root;
  *y = y_root;

375
  if (row >= priv->rows / 2)
376 377
    *y -= height;

378
  if (col >= priv->cols / 2)
379 380 381
    *x -= width;
}

382 383 384 385 386 387
/* depends on directionality */
static guint
get_cell_at_rowcol (GucharmapChartable *chartable,
                    gint            row,
                    gint            col)
{
388
  GucharmapChartablePrivate *priv = chartable->priv;
389
  GtkWidget *widget = GTK_WIDGET (chartable);
390

391
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
392
    return priv->page_first_cell + row * priv->cols + (priv->cols - col - 1);
393
  else
394
    return priv->page_first_cell + row * priv->cols + col;
395 396 397 398 399 400 401
}

/* Depends on directionality. Column 0 is the furthest left.  */
gint
_gucharmap_chartable_cell_column (GucharmapChartable *chartable,
                              guint cell)
{
402
  GucharmapChartablePrivate *priv = chartable->priv;
403
  GtkWidget *widget = GTK_WIDGET (chartable);
404

405
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
406
    return priv->cols - (cell - priv->page_first_cell) % priv->cols - 1;
407
  else
408
    return (cell - priv->page_first_cell) % priv->cols;
409 410 411 412 413 414
}

/* not all columns are necessarily the same width because of padding */
gint
_gucharmap_chartable_column_width (GucharmapChartable *chartable, gint col)
{
415 416 417
  GucharmapChartablePrivate *priv = chartable->priv;
  int num_padded_columns = priv->n_padded_columns;
  int min_col_w = priv->minimal_column_width;
418

419
  if (priv->cols - col <= num_padded_columns)
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    return min_col_w + 1;
  else
    return min_col_w;
}

/* calculates the position of the left end of the column (just to the right
 * of the left border) */
/* XXX: calling this repeatedly is not the most efficient, but it probably
 * is the most readable */
gint
_gucharmap_chartable_x_offset (GucharmapChartable *chartable, gint col)
{
  gint c, x;

  for (c = 0, x = 1;  c < col;  c++)
    x += _gucharmap_chartable_column_width (chartable, c);

  return x;
}

/* not all rows are necessarily the same height because of padding */
gint
_gucharmap_chartable_row_height (GucharmapChartable *chartable, gint row)
{
444 445 446
  GucharmapChartablePrivate *priv = chartable->priv;
  int num_padded_rows = priv->n_padded_rows;
  int min_row_h = priv->minimal_row_height;
447

448
  if (priv->rows - row <= num_padded_rows)
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
    return min_row_h + 1;
  else
    return min_row_h;
}

/* calculates the position of the top end of the row (just below the top
 * border) */
/* XXX: calling this repeatedly is not the most efficient, but it probably
 * is the most readable */
gint
_gucharmap_chartable_y_offset (GucharmapChartable *chartable, gint row)
{
  gint r, y;

  for (r = 0, y = 1;  r < row;  r++)
    y += _gucharmap_chartable_row_height (chartable, r);

  return y;
}

/* returns the font family of the last glyph item in the first line of the
 * layout; should be freed by caller */
static gchar *
get_font (PangoLayout *layout)
{
  PangoLayoutLine *line;
  PangoGlyphItem *glyph_item;
  PangoFont *font;
  GSList *run_node;
  gchar *family;
  PangoFontDescription *font_desc;

  line = pango_layout_get_line (layout, 0);

  /* get to the last glyph_item (the one with the character we're drawing */
  for (run_node = line->runs;  
       run_node && run_node->next;  
       run_node = run_node->next);

  if (run_node)
    {
      glyph_item = run_node->data;
      font = glyph_item->item->analysis.font;
      font_desc = pango_font_describe (font);

      family = g_strdup (pango_font_description_get_family (font_desc));

      pango_font_description_free (font_desc);
    }
  else
    family = NULL;

  return family;
}

/* font_family (if not null) gets filled in with the actual font family
 * used to draw the character */
static PangoLayout *
layout_scaled_glyph (GucharmapChartable *chartable, 
                     gunichar uc, 
509 510
                     double font_factor,
                     char **font_family)
511
{
512
  GucharmapChartablePrivate *priv = chartable->priv;
513 514 515 516
  PangoFontDescription *font_desc;
  PangoLayout *layout;
  gchar buf[11];

517
  font_desc = pango_font_description_copy (priv->font_desc);
518 519 520 521 522 523 524

  if (pango_font_description_get_size_is_absolute (priv->font_desc))
    pango_font_description_set_absolute_size (font_desc,
                                              font_factor * pango_font_description_get_size (priv->font_desc));
  else
    pango_font_description_set_size (font_desc,
                                     font_factor * pango_font_description_get_size (priv->font_desc));
525

526
  gucharmap_chartable_ensure_pango_layout (chartable);
527
  layout = pango_layout_new (pango_layout_get_context (priv->pango_layout));
528 529 530 531 532 533

  pango_layout_set_font_description (layout, font_desc);

  buf[gucharmap_unichar_to_printable_utf8 (uc, buf)] = '\0';
  pango_layout_set_text (layout, buf, -1);

534 535 536 537 538 539 540 541 542 543
  if (priv->font_fallback == FALSE) 
    {
      PangoAttrList *list;

      list = pango_attr_list_new ();
      pango_attr_list_insert (list, pango_attr_fallback_new (FALSE));
      pango_layout_set_attributes (layout, list);
      pango_attr_list_unref (list);
    }

544 545 546 547 548 549 550 551
  if (font_family)
    *font_family = get_font (layout);

  pango_font_description_free (font_desc);

  return layout;
}

552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
static cairo_surface_t *
create_glyph_surface (GucharmapChartable *chartable,
                      gunichar wc,
                      double font_factor,
                      gboolean draw_font_family,
                      int *zoom_surface_width,
                      int *zoom_surface_height)
{
  GtkWidget *widget = GTK_WIDGET (chartable);
  enum { PADDING = 8 };

  PangoLayout *pango_layout, *pango_layout2 = NULL;
  PangoRectangle char_rect, family_rect;
  gint width, height;
  GtkStyle *style;
  char *family;
  cairo_surface_t *surface;
  cairo_t *cr;

  /* Apply the scaling.  Unfortunately not all fonts seem to be scalable.
   * We could fall back to GdkPixbuf scaling, but that looks butt ugly :-/
   */
  pango_layout = layout_scaled_glyph (chartable, wc,
                                      font_factor, &family);
  pango_layout_get_pixel_extents (pango_layout, &char_rect, NULL);

  if (draw_font_family)
    {
      if (family == NULL)
        family = g_strdup (_("[not a printable character]"));

      pango_layout2 = gtk_widget_create_pango_layout (GTK_WIDGET (chartable), family);
      pango_layout_get_pixel_extents (pango_layout2, NULL, &family_rect);

      /* Make the GdkPixmap large enough to account for possible offsets in the
       * ink extents of the glyph. */
      width  = MAX (char_rect.width, family_rect.width)  + 2 * PADDING;
      height = family_rect.height + char_rect.height + 4 * PADDING;
    }
  else
    {
      width  = char_rect.width + 2 * PADDING;
      height = char_rect.height + 2 * PADDING;
    }

  style = gtk_widget_get_style (widget);

  surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
                                               CAIRO_CONTENT_COLOR,
                                               width, height);
  cr = cairo_create (surface);

  gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
  cairo_rectangle (cr, 0, 0, width, height);
  cairo_fill (cr);

  gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_INSENSITIVE]);
  cairo_set_line_width (cr, 1);
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
  cairo_rectangle (cr, 1.5, 1.5, width - 3, height - 3);
  cairo_stroke (cr);

  /* Now draw the glyph.  The coordinates are adapted
   * in order to compensate negative char_rect offsets.
   */
  gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
  cairo_move_to (cr, -char_rect.x + PADDING, -char_rect.y + PADDING);
  pango_cairo_show_layout (cr, pango_layout);
  g_object_unref (pango_layout);

  if (draw_font_family)
    {
      cairo_set_line_width (cr, 1);
      cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
      gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
      cairo_move_to (cr, 6 + 1 + .5, char_rect.height + 2 * PADDING + .5);
      cairo_line_to (cr, width - 3 - 6 - .5, char_rect.height + 2 * PADDING + .5);
      cairo_stroke (cr);

      gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
      cairo_move_to (cr, PADDING, height - PADDING - family_rect.height);
      /* FIXME: clip here? */
      pango_cairo_show_layout (cr, pango_layout2);

      g_object_unref (pango_layout2);
    }

  g_free (family);

  cairo_destroy (cr);

  if (zoom_surface_width)
    *zoom_surface_width = width;
  if (zoom_surface_height)
    *zoom_surface_height = height;

  return surface;
}

static GdkPixbuf *
create_glyph_pixbuf (GucharmapChartable *chartable,
                     gunichar wc,
                     double font_factor,
                     gboolean draw_font_family,
                     int *zoom_surface_width,
                     int *zoom_surface_height)
{
  cairo_surface_t *surface;
  int width, height;
  GdkPixbuf *pixbuf;

  surface = create_glyph_surface (chartable, wc, font_factor, draw_font_family,
                                  &width, &height);
665
  pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
666 667 668 669 670 671 672 673 674 675
  cairo_surface_destroy (surface);

  if (zoom_surface_width)
    *zoom_surface_width = width;
  if (zoom_surface_height)
    *zoom_surface_height = height;

  return pixbuf;
}

676 677 678 679
/* places the zoom window toward the inside of the coordinates */
static void
place_zoom_window (GucharmapChartable *chartable, gint x_root, gint y_root)
{
680
  GucharmapChartablePrivate *priv = chartable->priv;
681
  int x, y;
682

683
  if (!priv->zoom_window)
684
    return;
685

686 687 688
  get_appropriate_upper_left_xy (chartable,
                                 priv->zoom_image_width,
                                 priv->zoom_image_height,
689
                                 x_root, y_root, &x, &y);
690
  gtk_window_move (GTK_WINDOW (priv->zoom_window), x, y);
691 692
}

693 694 695
static void
place_zoom_window_on_active_cell (GucharmapChartable *chartable)
{
696
  GucharmapChartablePrivate *priv = chartable->priv;
697 698
  GdkRectangle rect, keepout_rect;

699
  if (!priv->zoom_window)
700 701 702 703 704
    return;

  get_active_cell_rect (chartable, &keepout_rect);

  rect.x = rect.y = 0;
705 706
  rect.width = priv->zoom_image_width;
  rect.height = priv->zoom_image_height;
707 708 709 710

  position_rectangle_on_screen (GTK_WIDGET (chartable),
                                &rect,
                                &keepout_rect);
711
  gtk_window_move (GTK_WINDOW (priv->zoom_window), rect.x, rect.y);
712 713
}

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
static int
get_font_size_px (GucharmapChartable *chartable)
{
  GucharmapChartablePrivate *priv = chartable->priv;
  GtkWidget *widget = GTK_WIDGET (chartable);
  GdkScreen *screen;
  double resolution;
  int font_size;

  g_assert (priv->font_desc != NULL);

  screen = gtk_widget_get_screen (widget);
  resolution = gdk_screen_get_resolution (screen);
  if (resolution < 0.0) /* will be -1 if the resolution is not defined in the GdkScreen */
    resolution = 96.0;

  if (pango_font_description_get_size_is_absolute (priv->font_desc))
    font_size = pango_font_description_get_size (priv->font_desc);
  else
    font_size = ((double) pango_font_description_get_size (priv->font_desc)) * resolution / 72.0;

  if (PANGO_PIXELS (font_size) <= 0)
    font_size = DEFAULT_FONT_SIZE * resolution / 72.0;

  return PANGO_PIXELS (font_size);
}

741 742 743
static void
update_zoom_window (GucharmapChartable *chartable)
{
744
  GucharmapChartablePrivate *priv = chartable->priv;
745 746 747
  GtkWidget *widget = GTK_WIDGET (chartable);
  double scale;
  int font_size_px, screen_height;
748
  GdkPixbuf *pixbuf;
749

Christian Persch's avatar
Christian Persch committed
750 751 752
  if (priv->zoom_window == NULL)
    return;

753 754 755 756 757
  font_size_px = get_font_size_px (chartable);
  screen_height = gdk_screen_get_height (gtk_widget_get_screen (widget));

  scale = (0.3 * screen_height) / (FACTOR_WIDTH * font_size_px);
  scale = CLAMP (scale, 1.0, 12.0);
758

759 760 761 762 763 764 765 766 767 768 769 770 771
  pixbuf = create_glyph_pixbuf (chartable,
                                gucharmap_chartable_get_active_character (chartable),
                                scale,
                                TRUE,
                                &priv->zoom_image_width,
                                &priv->zoom_image_height);
  gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (priv->zoom_window))),
                             pixbuf);
  g_object_unref (pixbuf);

  gtk_window_resize (GTK_WINDOW (priv->zoom_window),
                     priv->zoom_image_width, priv->zoom_image_height);
}
772 773 774 775

static void
make_zoom_window (GucharmapChartable *chartable)
{
776
  GucharmapChartablePrivate *priv = chartable->priv;
777
  GtkWidget *widget = GTK_WIDGET (chartable);
778
  GtkWidget *image;
779 780

  /* if there is already a zoom window, do nothing */
781
  if (priv->zoom_window || !priv->zoom_mode_enabled)
782 783
    return;

784
  priv->zoom_window = gtk_window_new (GTK_WINDOW_POPUP);
785 786 787
  /* For wayland, we need to "attach" the popup to the toplevel */
  gtk_window_set_transient_for (GTK_WINDOW (priv->zoom_window),
                                GTK_WINDOW (gtk_widget_get_toplevel (widget)));
788 789
  gtk_window_set_resizable (GTK_WINDOW (priv->zoom_window), FALSE);
  gtk_window_set_screen (GTK_WINDOW (priv->zoom_window),
790 791
                         gtk_widget_get_screen (widget));

792 793 794
  image = gtk_image_new ();
  gtk_container_add (GTK_CONTAINER (priv->zoom_window), image);
  gtk_widget_show (image);
795 796 797 798 799
}

static void
destroy_zoom_window (GucharmapChartable *chartable)
{
800 801 802
  GucharmapChartablePrivate *priv = chartable->priv;

  if (priv->zoom_window)
803 804 805 806
    {
      GtkWidget *widget = GTK_WIDGET (chartable);
      GtkWidget *zoom_window;

807 808
      zoom_window = priv->zoom_window;
      priv->zoom_window = NULL;
809

810
      gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
811 812 813 814
      gtk_widget_destroy (zoom_window);
    }
}

815 816 817
static void
gucharmap_chartable_show_zoom (GucharmapChartable *chartable)
{
818 819 820
  GucharmapChartablePrivate *priv = chartable->priv;

  if (!priv->zoom_mode_enabled)
821 822 823 824 825
    return;

  make_zoom_window (chartable);
  update_zoom_window (chartable);

826
  place_zoom_window_on_active_cell (chartable);
827

828
  gtk_widget_show (priv->zoom_window);
829 830 831 832 833 834 835 836 837 838 839 840

  g_object_notify (G_OBJECT (chartable), "zoom-showing");
}

static void
gucharmap_chartable_hide_zoom (GucharmapChartable *chartable)
{
  destroy_zoom_window (chartable);

  g_object_notify (G_OBJECT (chartable), "zoom-showing");
}

841
static gunichar
Christian Persch's avatar
Christian Persch committed
842 843
get_cell_at_xy (GucharmapChartable *chartable,
                gint            x,
844 845
                gint            y)
{
846
  GucharmapChartablePrivate *priv = chartable->priv;
847 848 849
  gint r, c, x0, y0;
  guint cell;

850
  for (c = 0, x0 = 0;  x0 <= x && c < priv->cols;  c++)
851 852
    x0 += _gucharmap_chartable_column_width (chartable, c);

853
  for (r = 0, y0 = 0;  y0 <= y && r < priv->rows;  r++)
854 855 856 857 858 859
    y0 += _gucharmap_chartable_row_height (chartable, r);

  /* cell = rowcol_to_unichar (chartable, r-1, c-1); */
  cell = get_cell_at_rowcol (chartable, r-1, c-1);

  /* XXX: check this somewhere else? */
860 861
  if (cell > priv->last_cell)
    return priv->last_cell;
862 863 864 865 866

  return cell;
}

static void
Christian Persch's avatar
Christian Persch committed
867 868
draw_character (GucharmapChartable *chartable,
                cairo_t            *cr,
869
                cairo_rectangle_int_t  *rect,
Christian Persch's avatar
Christian Persch committed
870
                gint            row,
871 872
                gint            col)
{
873
  GucharmapChartablePrivate *priv = chartable->priv;
874
  GtkWidget *widget = GTK_WIDGET (chartable);
875
  int n, char_width, char_height;
876 877
  gunichar wc;
  guint cell;
878
  GtkStyle *style;
Christian Persch's avatar
Christian Persch committed
879
  GdkColor *color;
880 881 882
  gchar buf[10];

  cell = get_cell_at_rowcol (chartable, row, col);
883
  wc = gucharmap_codepoint_list_get_char (priv->codepoint_list, cell);
884

885 886 887
  if (wc > UNICHAR_MAX ||
      !gucharmap_unichar_validate (wc) || 
      !gucharmap_unichar_isdefined (wc))
888 889
    return;

890 891 892 893 894 895 896 897 898 899
  n = gucharmap_unichar_to_printable_utf8 (wc, buf);
  pango_layout_set_text (priv->pango_layout, buf, n);

  /* Keep the square empty if font fallback is disabled and the
   * font has no glyph for this cell.
   */
  if (!priv->font_fallback &&
      pango_layout_get_unknown_glyphs_count (priv->pango_layout) > 0)
    return;

Christian Persch's avatar
Christian Persch committed
900 901
  cairo_save (cr);

902 903 904
  style = gtk_widget_get_style (widget);

  if (gtk_widget_has_focus (widget) && (gint)cell == priv->active_cell)
Christian Persch's avatar
Christian Persch committed
905
    color = &style->text[GTK_STATE_SELECTED];
906
  else if ((gint)cell == priv->active_cell)
Christian Persch's avatar
Christian Persch committed
907
    color = &style->text[GTK_STATE_ACTIVE];
908
  else
Christian Persch's avatar
Christian Persch committed
909 910 911
    color = &style->text[GTK_STATE_NORMAL];

  gdk_cairo_set_source_color (cr, color);
912

913 914 915 916
  cairo_rectangle (cr, 
                   rect->x + 1, rect->y + 1, 
                   rect->width - 2, rect->height - 2);
  cairo_clip (cr);
917

918
  pango_layout_get_pixel_size (priv->pango_layout, &char_width, &char_height);
919 920 921
  cairo_move_to (cr, 
                 rect->x + (rect->width - char_width - 2 + 1) / 2,
                 rect->y + (rect->height - char_height - 2 + 1) / 2);
Christian Persch's avatar
Christian Persch committed
922
  pango_cairo_show_layout (cr, priv->pango_layout);
923

Christian Persch's avatar
Christian Persch committed
924
  cairo_restore (cr);
925 926 927 928 929 930
}

static void
expose_square (GucharmapChartable *chartable, gint row, gint col)
{
  GtkWidget *widget = GTK_WIDGET (chartable);
931

932 933 934 935 936 937 938 939
  gtk_widget_queue_draw_area (widget,
                              _gucharmap_chartable_x_offset (chartable, col),
                              _gucharmap_chartable_y_offset (chartable, row),
                              _gucharmap_chartable_column_width (chartable, col) - 1,
                              _gucharmap_chartable_row_height (chartable, row) - 1);
}

static void
Christian Persch's avatar
Christian Persch committed
940 941
expose_cell (GucharmapChartable *chartable,
             guint cell)
942
{
943 944 945
  GucharmapChartablePrivate *priv = chartable->priv;

  gint row = (cell - priv->page_first_cell) / priv->cols;
946 947
  gint col = _gucharmap_chartable_cell_column (chartable, cell);

948
  if (row >= 0 && row < priv->rows && col >= 0 && col < priv->cols)
Christian Persch's avatar
Christian Persch committed
949
    expose_square (chartable, row, col);
950 951 952
}

static void
Christian Persch's avatar
Christian Persch committed
953 954
draw_square_bg (GucharmapChartable *chartable,
                cairo_t *cr,
955
                cairo_rectangle_int_t  *rect,
Christian Persch's avatar
Christian Persch committed
956 957
                gint row,
                gint col)
958
{
959
  GucharmapChartablePrivate *priv = chartable->priv;
960
  GtkWidget *widget = GTK_WIDGET (chartable);
Christian Persch's avatar
Christian Persch committed
961 962 963 964
  GdkColor *untinted;
  GtkStyle *style;
  guint cell;
  gunichar wc;
Christian Persch's avatar
Christian Persch committed
965

Christian Persch's avatar
Christian Persch committed
966
  cairo_save (cr);
967

Christian Persch's avatar
Christian Persch committed
968 969
  cell = get_cell_at_rowcol (chartable, row, col);
  wc = gucharmap_codepoint_list_get_char (priv->codepoint_list, cell);
970

Christian Persch's avatar
Christian Persch committed
971 972 973 974 975 976 977 978 979 980 981 982 983 984
  style = gtk_widget_get_style (widget);

  if (gtk_widget_has_focus (widget) && (gint)cell == priv->active_cell)
    untinted = &style->base[GTK_STATE_SELECTED];
  else if ((gint)cell == priv->active_cell)
    untinted = &style->base[GTK_STATE_ACTIVE];
  else if ((gint)cell > priv->last_cell)
    untinted = &style->dark[GTK_STATE_NORMAL];
  else if (! gucharmap_unichar_validate (wc))
    untinted = &style->fg[GTK_STATE_INSENSITIVE];
  else if (! gucharmap_unichar_isdefined (wc))
    untinted = &style->bg[GTK_STATE_INSENSITIVE];
  else
    untinted = &style->base[GTK_STATE_NORMAL];
985

Christian Persch's avatar
Christian Persch committed
986 987 988
  gdk_cairo_set_source_color (cr, untinted);
  cairo_set_line_width (cr, 1);
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
989

990
  cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
Christian Persch's avatar
Christian Persch committed
991 992 993
  cairo_fill (cr);

  cairo_restore (cr);
994 995 996
}

static void
Christian Persch's avatar
Christian Persch committed
997 998
draw_borders (GucharmapChartable *chartable,
              cairo_t *cr)
999
{
1000
  GucharmapChartablePrivate *priv = chartable->priv;
1001
  GtkWidget *widget = GTK_WIDGET (chartable);
Christian Persch's avatar
Christian Persch committed
1002
  GtkAllocation *allocation;
Christian Persch's avatar
Christian Persch committed
1003 1004
  GtkStyle *style;
  gint x, y, col, row;
Christian Persch's avatar
Christian Persch committed
1005
  GtkAllocation widget_allocation;
1006

Christian Persch's avatar
Christian Persch committed
1007 1008
  gtk_widget_get_allocation (widget, &widget_allocation);
  allocation = &widget_allocation;
1009

Christian Persch's avatar
Christian Persch committed
1010
  cairo_save (cr);
Christian Persch's avatar
Christian Persch committed
1011

Christian Persch's avatar
Christian Persch committed
1012 1013
  /* dark_gc[GTK_STATE_NORMAL] seems to be what is used to draw the borders
   * around widgets, so we use it for the lines */
1014

Christian Persch's avatar
Christian Persch committed
1015 1016 1017 1018 1019
  style = gtk_widget_get_style (widget);
  gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);

  cairo_set_line_width (cr, 1); /* FIXME themeable? */
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
1020

Christian Persch's avatar
Christian Persch committed
1021 1022 1023
  /* vertical lines */
  cairo_move_to (cr, .5, .5);
  cairo_line_to (cr, .5, allocation->height - .5);
1024

Christian Persch's avatar
Christian Persch committed
1025 1026 1027 1028 1029
  for (col = 0, x = 0;  col < priv->cols;  col++)
    {
      x += _gucharmap_chartable_column_width (chartable, col);
      cairo_move_to (cr, x + .5, .5);
      cairo_line_to (cr, x + .5, allocation->height - .5);
1030 1031
    }

Christian Persch's avatar
Christian Persch committed
1032 1033 1034 1035 1036
  /* horizontal lines */
  cairo_move_to (cr, .5, .5);
  cairo_line_to (cr, allocation->width - .5, .5);

  for (row = 0, y = 0;  row < priv->rows;  row++)
1037
    {
Christian Persch's avatar
Christian Persch committed
1038
      y += _gucharmap_chartable_row_height (chartable, row);
1039

Christian Persch's avatar
Christian Persch committed
1040 1041
      cairo_move_to (cr, .5, y + .5);
      cairo_line_to (cr, allocation->width - .5, y + .5);
1042
    }
Christian Persch's avatar
Christian Persch committed
1043 1044 1045

  cairo_stroke (cr);
  cairo_restore (cr);
1046 1047
}

Christian Persch's avatar
Christian Persch committed
1048 1049
static void
update_scrollbar_adjustment (GucharmapChartable *chartable)
1050
{
1051
  GucharmapChartablePrivate *priv = chartable->priv;
Christian Persch's avatar
Christian Persch committed
1052
  GtkAdjustment *vadjustment = priv->vadjustment;
1053

Christian Persch's avatar
Christian Persch committed
1054 1055
  if (!vadjustment)
    return;
1056

Christian Persch's avatar
Christian Persch committed
1057
  gtk_adjustment_configure (vadjustment,
1058 1059 1060 1061 1062
                            priv->page_first_cell / priv->cols,
                            0 /* lower */,
                            priv->last_cell / priv->cols + 1 /* upper */,
                            3 /* step increment */,
                            priv->rows /* page increment */,
Christian Persch's avatar
Christian Persch committed
1063 1064
                            priv->rows);
}
1065

Christian Persch's avatar
Christian Persch committed
1066 1067 1068 1069 1070 1071 1072
static void
gucharmap_chartable_set_active_cell (GucharmapChartable *chartable,
                                     int cell)
{
  GtkWidget *widget = GTK_WIDGET (chartable);
  GucharmapChartablePrivate *priv = chartable->priv;
  int old_active_cell, old_page_first_cell;
1073

Christian Persch's avatar
Christian Persch committed
1074 1075
  if (cell == priv->active_cell)
    return;
1076

Christian Persch's avatar
Christian Persch committed
1077 1078 1079 1080
  if (cell < 0)
    cell = 0;
  else if (cell > priv->last_cell)
    cell = priv->last_cell;
1081

Christian Persch's avatar
Christian Persch committed
1082 1083
  old_active_cell = priv->active_cell;
  old_page_first_cell = priv->page_first_cell;
1084

Christian Persch's avatar
Christian Persch committed
1085
  priv->active_cell = cell;
1086

Christian Persch's avatar
Christian Persch committed
1087 1088 1089 1090
  if (cell < priv->page_first_cell || cell >= priv->page_first_cell + priv->page_size)
    {
      int old_row = old_active_cell / priv->cols;
      int new_row = cell / priv->cols;
1091 1092 1093 1094
      int new_page_first_cell = old_page_first_cell + (new_row - old_row) * priv->cols;
      int last_page_first_cell = (priv->last_cell / priv->cols - priv->rows + 1) * priv->cols;

      priv->page_first_cell = CLAMP (new_page_first_cell, 0, last_page_first_cell);
1095

Christian Persch's avatar
Christian Persch committed
1096
      if (priv->vadjustment)
1097
        gtk_adjustment_set_value (priv->vadjustment, priv->page_first_cell / priv->cols);
1098
    }
Christian Persch's avatar
Christian Persch committed
1099 1100 1101 1102
  else if (gtk_widget_get_realized (widget)) {
    expose_cell (chartable, old_active_cell);
    expose_cell (chartable, cell);
  }
1103

Christian Persch's avatar
Christian Persch committed
1104 1105 1106 1107
  g_object_notify (G_OBJECT (chartable), "active-character");

  update_zoom_window (chartable); 
  place_zoom_window_on_active_cell (chartable);
1108 1109 1110
}

static void
Christian Persch's avatar
Christian Persch committed
1111 1112
set_active_char (GucharmapChartable *chartable,
                 gunichar        wc)
1113
{
1114
  GucharmapChartablePrivate *priv = chartable->priv;
1115

Christian Persch's avatar
Christian Persch committed
1116 1117 1118
  guint cell = gucharmap_codepoint_list_get_index (priv->codepoint_list, wc);
  if (cell == -1) {
    gtk_widget_error_bell (GTK_WIDGET (chartable));
1119
    return;
Christian Persch's avatar
Christian Persch committed
1120
  }
1121

Christian Persch's avatar
Christian Persch committed
1122
  gucharmap_chartable_set_active_cell (chartable, cell);
1123 1124 1125
}

static void
Christian Persch's avatar
Christian Persch committed
1126 1127
vadjustment_value_changed_cb (GtkAdjustment *vadjustment, 
                              GucharmapChartable *chartable)
1128
{
Christian Persch's avatar
Christian Persch committed
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
  GucharmapChartablePrivate *priv = chartable->priv;
  int row, r, c, old_page_first_cell, old_active_cell, first_cell;

  row = (int) gtk_adjustment_get_value (vadjustment);

  if (row < 0 || row > priv->last_cell / priv->cols)
    row = 0;

  first_cell = row * priv->cols;

  gtk_widget_queue_draw (GTK_WIDGET (chartable));

  old_page_first_cell = priv->page_first_cell;
  old_active_cell = priv->active_cell;

  priv->page_first_cell = first_cell;

  /* character is still on the visible page */
  if (priv->active_cell - priv->page_first_cell >= 0
      && priv->active_cell - priv->page_first_cell < priv->page_size)
    return;

  c = old_active_cell % priv->cols;

  if (priv->page_first_cell < old_page_first_cell)
    r = priv->rows - 1;
  else
    r = 0;

  gucharmap_chartable_set_active_cell (chartable, priv->page_first_cell + r * priv->cols + c);
1159 1160
}

1161 1162
/* GtkWidget class methods */

1163 1164 1165 1166 1167 1168 1169 1170 1171
/*  - single click with left button: activate character under pointer
 *  - double-click with left button: add active character to text_to_copy
 *  - single-click with middle button: jump to selection_primary
 */
static gboolean
gucharmap_chartable_button_press (GtkWidget *widget,
                                  GdkEventButton *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1172
  GucharmapChartablePrivate *priv = chartable->priv;
1173 1174 1175 1176 1177 1178

  /* in case we lost keyboard focus and are clicking to get it back */
  gtk_widget_grab_focus (widget);

  if (event->button == 1)
    {
1179 1180
      priv->click_x = event->x;
      priv->click_y = event->y;
1181 1182 1183 1184 1185
    }

  /* double-click */
  if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
    {
1186
      g_signal_emit (chartable, signals[ACTIVATE], 0);
1187 1188 1189 1190
    }
  /* single-click */ 
  else if (event->button == 1 && event->type == GDK_BUTTON_PRESS) 
    {
Christian Persch's avatar
Christian Persch committed
1191
      gucharmap_chartable_set_active_cell (chartable, get_cell_at_xy (chartable, event->x, event->y));
1192 1193 1194
    }
  else if (event->button == 3)
    {
Christian Persch's avatar
Christian Persch committed
1195
      gucharmap_chartable_set_active_cell (chartable, get_cell_at_xy (chartable, event->x, event->y));
Christian Persch's avatar
Christian Persch committed
1196
      gucharmap_chartable_show_zoom (chartable);
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
    }

  /* XXX: [need to return false so it gets drag events] */
  /* actually return true because we handle drag_begin because of
   * http://bugzilla.gnome.org/show_bug.cgi?id=114534 */
  return TRUE;
}

static gboolean
gucharmap_chartable_button_release (GtkWidget *widget,
                                    GdkEventButton *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
  gboolean (* button_press_event) (GtkWidget *, GdkEventButton *) =
    GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->button_release_event;

1213 1214
  if (event->button == 3)
    gucharmap_chartable_hide_zoom (chartable);
1215 1216 1217 1218 1219 1220

  if (button_press_event)
    return button_press_event (widget, event);
  return FALSE;
}

1221 1222 1223 1224 1225
static void
gucharmap_chartable_drag_begin (GtkWidget *widget,
                                GdkDragContext *context)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1226 1227
  double scale;
  int font_size_px, screen_height;
1228
  cairo_surface_t *drag_surface;
1229 1230 1231 1232 1233 1234

  font_size_px = get_font_size_px (chartable);
  screen_height = gdk_screen_get_height (gtk_widget_get_screen (widget));

  scale = (0.3 * screen_height) / (FACTOR_WIDTH * font_size_px);
  scale = CLAMP (scale, 1.0, 5.0);
1235

1236 1237 1238 1239 1240 1241
  drag_surface = create_glyph_surface (chartable,
                                       gucharmap_chartable_get_active_character (chartable),
                                       scale,
                                       FALSE, NULL, NULL);
  gtk_drag_set_icon_surface (context, drag_surface);
  cairo_surface_destroy (drag_surface);
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254

  /* no need to chain up */
}

static void
gucharmap_chartable_drag_data_get (GtkWidget *widget, 
                                   GdkDragContext *context,
                                   GtkSelectionData *selection_data,
                                   guint info,
                                   guint time)

{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1255
  GucharmapChartablePrivate *priv = chartable->priv;
1256 1257 1258
  gchar buf[7];
  gint n;

1259
  n = g_unichar_to_utf8 (gucharmap_codepoint_list_get_char (priv->codepoint_list, priv->active_cell), buf);
1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273
  gtk_selection_data_set_text (selection_data, buf, n);

  /* no need to chain up */
}

static void
gucharmap_chartable_drag_data_received (GtkWidget *widget,
                                        GdkDragContext *context,
                                        gint x, gint y,
                                        GtkSelectionData *selection_data,
                                        guint info,
                                        guint time)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1274
  GucharmapChartablePrivate *priv = chartable->priv;
1275 1276 1277
  gchar *text;
  gunichar wc;

Christian Persch's avatar
Christian Persch committed
1278 1279
  if (gtk_selection_data_get_length (selection_data) <= 0 ||
      gtk_selection_data_get_data (selection_data) == NULL)
1280 1281 1282 1283 1284 1285 1286 1287 1288 1289
    return;

  text = (gchar *) gtk_selection_data_get_text (selection_data);

  if (text == NULL) /* XXX: say something in the statusbar? */
    return;

  wc = g_utf8_get_char_validated (text, -1);

  if (wc == (gunichar)(-2) || wc == (gunichar)(-1) || wc > UNICHAR_MAX)
Christian Persch's avatar
Christian Persch committed
1290
    gucharmap_chartable_emit_status_message (chartable, _("Unknown character, unable to identify."));
1291
  else if (gucharmap_codepoint_list_get_index (priv->codepoint_list, wc) == (guint)(-1))
Christian Persch's avatar
Christian Persch committed
1292
    gucharmap_chartable_emit_status_message (chartable, _("Not found."));
1293 1294
  else
    {
Christian Persch's avatar
Christian Persch committed
1295
      gucharmap_chartable_emit_status_message (chartable, _("Character found."));
1296
      set_active_char (chartable, wc);
Christian Persch's avatar
Christian Persch committed
1297
      place_zoom_window_on_active_cell (chartable);
1298 1299 1300 1301 1302 1303 1304
    }

  g_free (text);

  /* no need to chain up */
}

Christian Persch's avatar
Christian Persch committed
1305 1306 1307
static gboolean
gucharmap_chartable_draw (GtkWidget *widget,
                          cairo_t *cr)
1308
{
1309
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1310
  GucharmapChartablePrivate *priv = chartable->priv;
Christian Persch's avatar
Christian Persch committed
1311
  GtkStyle *style;
1312
  int row, col;
Christian Persch's avatar
Christian Persch committed
1313 1314
  cairo_rectangle_int_t clip_rect;
  cairo_region_t *region;
1315

Christian Persch's avatar
Christian Persch committed
1316
  if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
1317
    return FALSE;
Christian Persch's avatar
Christian Persch committed
1318 1319 1320 1321 1322 1323 1324

  region = cairo_region_create_rectangle (&clip_rect);

  if (cairo_region_is_empty (region)) {
    cairo_region_destroy (region);
    return FALSE;
  }
1325

1326
#if 0
1327
  {
Christian Persch's avatar
Christian Persch committed
1328 1329 1330 1331
    int i, n_rects;

    n_rects = cairo_region_num_rectangles (event->region);

1332 1333 1334 1335 1336 1337 1338 1339 1340
    g_print ("Exposing area %d:%d@(%d,%d) with %d rects ", event->area.width, event->area.height,
             event->area.x, event->area.y, n_rects);
    for (i = 0; i < n_rects; ++i) {
      g_print ("[Rect %d:%d@(%d,%d)] ", rects[i].width, rects[i].height, rects[i].x, rects[i].y);
    }
    g_print ("\n");
  }
#endif

Christian Persch's avatar
Christian Persch committed
1341
  style = gtk_widget_get_style (widget);
Christian Persch's avatar
Christian Persch committed
1342
  gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
Christian Persch's avatar
Christian Persch committed
1343
  gdk_cairo_region (cr, region);
Christian Persch's avatar
Christian Persch committed
1344
  cairo_fill (cr);
Christian Persch's avatar
Christian Persch committed
1345

1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361
  if (priv->codepoint_list == NULL)
    goto expose_done;

  gucharmap_chartable_ensure_pango_layout (chartable);

  for (row = priv->rows - 1; row >= 0; --row)
    {
      for (col = priv->cols - 1; col >= 0; --col)
        {
          cairo_rectangle_int_t rect;

          rect.x = _gucharmap_chartable_x_offset (chartable, col);
          rect.y = _gucharmap_chartable_y_offset (chartable, row);
          rect.width = _gucharmap_chartable_column_width (chartable, col);
          rect.height = _gucharmap_chartable_row_height (chartable, row);

Christian Persch's avatar
Christian Persch committed
1362
          if (cairo_region_contains_rectangle (region, &rect) == CAIRO_REGION_OVERLAP_OUT)
1363 1364 1365 1366 1367 1368 1369 1370
            continue;

          draw_square_bg (chartable, cr, &rect, row, col);
          draw_character (chartable, cr, &rect, row, col);
        }
    }

  draw_borders (chartable, cr);
1371

1372
expose_done:
Christian Persch's avatar
Christian Persch committed
1373

Christian Persch's avatar
Christian Persch committed
1374
  cairo_region_destroy (region);
1375

1376
  /* no need to chain up */
1377 1378 1379
  return FALSE;
}

1380 1381 1382 1383 1384
static gboolean
gucharmap_chartable_focus_in_event (GtkWidget *widget, 
                                    GdkEventFocus *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1385
  GucharmapChartablePrivate *priv = chartable->priv;
1386

Christian Persch's avatar
Christian Persch committed
1387
  expose_cell (chartable, priv->active_cell);
1388 1389 1390 1391 1392 1393 1394 1395 1396

  return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->focus_in_event (widget, event);
}

static gboolean
gucharmap_chartable_focus_out_event (GtkWidget *widget,
                                     GdkEventFocus *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1397
  GucharmapChartablePrivate *priv = chartable->priv;
1398

1399
  gucharmap_chartable_hide_zoom (chartable);
1400

Christian Persch's avatar
Christian Persch committed
1401
  expose_cell (chartable, priv->active_cell);
1402 1403 1404 1405 1406 1407

  /* FIXME: the parent's handler already does a draw... */

  return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->focus_out_event (widget, event);
}

1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418
static gboolean
gucharmap_chartable_key_press_event (GtkWidget *widget,
                                     GdkEventKey *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);

  if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK))
    return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->key_press_event (widget, event);

  switch (event->keyval)
    {
1419
      case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
1420
        gucharmap_chartable_show_zoom (chartable);
1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435
        break;

      /* pass on other keys, like tab and stuff that shifts focus */
      default:
        break;
    }

  return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->key_press_event (widget, event);
}

static gboolean
gucharmap_chartable_key_release_event (GtkWidget *widget,
                                       GdkEventKey *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1436
  
1437 1438 1439 1440 1441
  switch (event->keyval)
    {
      /* XXX: If the group(shift_toggle) Xkb option is set, then releasing
       * the shift key gives either ISO_Next_Group or ISO_Prev_Group. Is
       * there a better way to handle this case? */
1442 1443 1444 1445
      case GDK_KEY_Shift_L:
      case GDK_KEY_Shift_R:
      case GDK_KEY_ISO_Next_Group:
      case GDK_KEY_ISO_Prev_Group:
1446
        gucharmap_chartable_hide_zoom (chartable);
1447 1448 1449 1450 1451 1452
        break;
    }

  return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->key_release_event (widget, event);
}

1453 1454 1455 1456 1457
static gboolean
gucharmap_chartable_motion_notify (GtkWidget *widget,
                                   GdkEventMotion *event)
{
  GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1458
  GucharmapChartablePrivate *priv = chartable->priv;
1459 1460 1461
  gboolean (* motion_notify_event) (GtkWidget *, GdkEventMotion *) =
    GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->motion_notify_event;

1462
  if ((event->state & GDK_BUTTON1_MASK) &&
1463
      gtk_drag_check_threshold (widget,
1464 1465
                                priv->click_x,
                                priv->click_y,
1466
                                event->x,
1467 1468
                                event->y) &&
      gucharmap_unichar_validate (gucharmap_chartable_get_active_character (chartable)))
1469
    {
1470
      gtk_drag_begin (widget, priv->target_list,
1471 1472 1473 1474
                      GDK_ACTION_COPY, 1, (GdkEvent *) event);
    }

  if ((event->state & GDK_BUTTON3_MASK) != 0 &&
1475
      priv->zoom_window)
1476 1477 1478
    {
      guint cell = get_cell_at_xy (chartable, MAX (0, event->x), MAX (0, event->y));

1479
      if ((gint)cell != priv->active_cell)
1480
        {
1481
          gtk_widget_hide (priv->zoom_window);
Christian Persch's avatar
Christian Persch committed
1482
          gucharmap_chartable_set_active_cell (chartable, cell);
1483 1484 1485
        }

      place_zoom_window (chartable, event->x_root, event->y_root);
1486
      gtk_widget_show (priv->zoom_window);
1487 1488 1489 1490 1491 1492 1493
    }

  if (motion_notify_event)
    motion_notify_event (widget, event);
  return FALSE;
}

1494