1 /*
2  * Copyright © 2004 Noah Levitt
3  * Copyright © 2007, 2008, 2010 Christian Persch
4  *
5  * Some code copied from gtk+/gtk/gtkiconview:
6  * Copyright © 2002, 2004  Anders Carlsson <andersca@gnu.org>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 3 of the License, or (at your
11  * option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * 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 
23 #include <config.h>
24 
25 #include <glib/gi18n-lib.h>
26 #include <gtk/gtk.h>
27 
28 #include "gucharmap-marshal.h"
29 #include "gucharmap-chartable.h"
30 #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 #endif
38 
39 enum
40 {
41   ACTIVATE,
42   STATUS_MESSAGE,
43   MOVE_CURSOR,
44   COPY_CLIPBOARD,
45   PASTE_CLIPBOARD,
46   NUM_SIGNALS
47 };
48 
49 enum
50 {
51   PROP_0,
52   PROP_HADJUSTMENT,
53   PROP_VADJUSTMENT,
54   PROP_HSCROLL_POLICY,
55   PROP_VSCROLL_POLICY,
56   PROP_ACTIVE_CHAR,
57   PROP_CODEPOINT_LIST,
58   PROP_FONT_DESC,
59   PROP_FONT_FALLBACK,
60   PROP_SNAP_POW2,
61   PROP_ZOOM_ENABLED,
62   PROP_ZOOM_SHOWING
63 };
64 
65 static void gucharmap_chartable_class_init (GucharmapChartableClass *klass);
66 static void gucharmap_chartable_finalize   (GObject *object);
67 static void gucharmap_chartable_set_active_cell (GucharmapChartable *chartable,
68                                                  int cell);
69 
70 static guint signals[NUM_SIGNALS];
71 
72 #define DEFAULT_FONT_SIZE (20.0 * (double) PANGO_SCALE)
73 
74 /* These are chosen for compatibility with the older code that
75  * didn't scale the font size by resolution and used 3 and 2.5 here, resp.
76  * Where exactly these factors came from, I don't know.
77  */
78 #define FACTOR_WIDTH (2.25) /* 3 / (96 / 72) */
79 #define FACTOR_HEIGHT (1.875) /* 2.5 / (96 / 72) */
80 
81 /** Notes
82  *
83  * 1. Table geometry
84  * The allocated rectangle is divided into ::rows rows and ::col columns,
85  * numbered 0..rows-1 and 0..cols-1.
86  * The available width (height) is divided evenly between all columns (rows).
87  * The remaining space is distributed among the columns (rows) so that
88  * columns cols-n_padded_columns .. cols-1 (rows rows-n_padded_rows .. rows)
89  * are 1px wider (taller) than the others.
90  */
91 
92 /* ATK factory */
93 
94 #ifdef ENABLE_ACCESSIBLE
95 
96 typedef AtkObjectFactory      GucharmapChartableAccessibleFactory;
97 typedef AtkObjectFactoryClass GucharmapChartableAccessibleFactoryClass;
98 
99 static void
gucharmap_chartable_accessible_factory_init(GucharmapChartableAccessibleFactory * factory)100 gucharmap_chartable_accessible_factory_init (GucharmapChartableAccessibleFactory *factory)
101 {
102 }
103 
104 static AtkObject*
gucharmap_chartable_accessible_factory_create_accessible(GObject * obj)105 gucharmap_chartable_accessible_factory_create_accessible (GObject *obj)
106 {
107   return gucharmap_chartable_accessible_new (GUCHARMAP_CHARTABLE (obj));
108 }
109 
110 static GType
gucharmap_chartable_accessible_factory_get_accessible_type(void)111 gucharmap_chartable_accessible_factory_get_accessible_type (void)
112 {
113   return gucharmap_chartable_accessible_get_type ();
114 }
115 
116 static void
gucharmap_chartable_accessible_factory_class_init(AtkObjectFactoryClass * klass)117 gucharmap_chartable_accessible_factory_class_init (AtkObjectFactoryClass *klass)
118 {
119   klass->create_accessible = gucharmap_chartable_accessible_factory_create_accessible;
120   klass->get_accessible_type = gucharmap_chartable_accessible_factory_get_accessible_type;
121 }
122 
123 static GType gucharmap_chartable_accessible_factory_get_type (void);
G_DEFINE_TYPE(GucharmapChartableAccessibleFactory,gucharmap_chartable_accessible_factory,ATK_TYPE_OBJECT_FACTORY)124 G_DEFINE_TYPE (GucharmapChartableAccessibleFactory, gucharmap_chartable_accessible_factory, ATK_TYPE_OBJECT_FACTORY)
125 
126 #endif
127 
128 /* Type definition */
129 
130 G_DEFINE_TYPE_WITH_CODE (GucharmapChartable, gucharmap_chartable, GTK_TYPE_DRAWING_AREA,
131                          G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
132 
133 /* utility functions */
134 
135 static void
136 gucharmap_chartable_clear_pango_layout (GucharmapChartable *chartable)
137 {
138   GucharmapChartablePrivate *priv = chartable->priv;
139 
140   if (priv->pango_layout == NULL)
141     return;
142   g_object_unref (priv->pango_layout);
143   priv->pango_layout = NULL;
144 }
145 
146 static void
gucharmap_chartable_ensure_pango_layout(GucharmapChartable * chartable)147 gucharmap_chartable_ensure_pango_layout (GucharmapChartable *chartable)
148 {
149   GucharmapChartablePrivate *priv = chartable->priv;
150 
151   if (priv->pango_layout != NULL)
152     return;
153 
154   priv->pango_layout = gtk_widget_create_pango_layout (GTK_WIDGET (chartable), NULL);
155   pango_layout_set_font_description (priv->pango_layout,
156                                      priv->font_desc);
157 
158   if (priv->font_fallback == FALSE)
159     {
160       PangoAttrList *list;
161 
162       list = pango_attr_list_new ();
163       pango_attr_list_insert (list, pango_attr_fallback_new (FALSE));
164       pango_layout_set_attributes (priv->pango_layout, list);
165       pango_attr_list_unref (list);
166     }
167 }
168 
169 static void
gucharmap_chartable_set_font_desc_internal(GucharmapChartable * chartable,PangoFontDescription * font_desc)170 gucharmap_chartable_set_font_desc_internal (GucharmapChartable *chartable,
171                                             PangoFontDescription *font_desc /* adopting */)
172 {
173   GucharmapChartablePrivate *priv = chartable->priv;
174   GtkWidget *widget;
175 
176   if (priv->font_desc)
177     pango_font_description_free (priv->font_desc);
178 
179   priv->font_desc = font_desc;
180 
181   gucharmap_chartable_clear_pango_layout (chartable);
182 
183   widget = GTK_WIDGET (chartable);
184   if (gtk_widget_get_realized (widget))
185     gtk_widget_queue_resize (widget);
186 
187   g_object_notify (G_OBJECT (chartable), "font-desc");
188 }
189 
190 static void
gucharmap_chartable_emit_status_message(GucharmapChartable * chartable,const char * message)191 gucharmap_chartable_emit_status_message (GucharmapChartable *chartable,
192                                          const char *message)
193 {
194   g_signal_emit (chartable, signals[STATUS_MESSAGE], 0, message);
195 }
196 
197 typedef enum {
198   POSITION_DOWN_ALIGN_LEFT,
199   POSITION_DOWN_ALIGN_RIGHT,
200   POSITION_RIGHT_ALIGN_TOP,
201   POSITION_RIGHT_ALIGN_BOTTOM,
202   POSITION_TOP_ALIGN_LEFT,
203   POSITION_TOP_ALIGN_RIGHT,
204   POSITION_LEFT_ALIGN_TOP,
205   POSITION_LEFT_ALIGN_BOTTOM
206 } PositionType;
207 
208 static const PositionType rtl_position[] = {
209   POSITION_DOWN_ALIGN_RIGHT,
210   POSITION_DOWN_ALIGN_LEFT,
211   POSITION_LEFT_ALIGN_TOP,
212   POSITION_LEFT_ALIGN_BOTTOM,
213   POSITION_TOP_ALIGN_RIGHT,
214   POSITION_TOP_ALIGN_LEFT,
215   POSITION_RIGHT_ALIGN_TOP,
216   POSITION_RIGHT_ALIGN_BOTTOM
217 };
218 
219 /**
220  * position_rectangle:
221  * @rect: the rectangle to position. Inout; width and height must be initialised
222  * @target_rect: the rectangle to position @rect on
223  * @bounding_rect: the bounding rectangle
224  * @position: how to position the rectangle
225  * @direction: the text direction
226  *
227  * Returns: %TRUE if @rect could be positioned on @reference_point
228  * with positioning according to @gravity inside @bounding_rect
229  */
230 static gboolean
position_rectangle(GdkRectangle * position_rect,GdkRectangle * target_rect,GdkRectangle * bounding_rect,PositionType position,GtkTextDirection direction)231 position_rectangle (GdkRectangle *position_rect,
232                     GdkRectangle *target_rect,
233                     GdkRectangle *bounding_rect,
234                     PositionType position,
235                     GtkTextDirection direction)
236 {
237   GdkRectangle rect;
238 
239   if (direction == GTK_TEXT_DIR_RTL) {
240     position = rtl_position[position];
241   }
242 
243   rect.x = target_rect->x;
244   rect.y = target_rect->y;
245   rect.width = position_rect->width;
246   rect.height = position_rect->height;
247 
248   switch (position) {
249     case POSITION_DOWN_ALIGN_RIGHT:
250       rect.x -= rect.width - target_rect->width;
251       /* fall-through */
252     case POSITION_DOWN_ALIGN_LEFT:
253       rect.y += target_rect->height;
254       break;
255 
256     case POSITION_RIGHT_ALIGN_BOTTOM:
257       rect.y -= rect.height - target_rect->height;
258       /* fall-through */
259     case POSITION_RIGHT_ALIGN_TOP:
260       rect.x += target_rect->width;
261       break;
262 
263     case POSITION_TOP_ALIGN_RIGHT:
264       rect.x -= rect.width - target_rect->width;
265       /* fall-through */
266     case POSITION_TOP_ALIGN_LEFT:
267       rect.y -= rect.height;
268       break;
269 
270     case POSITION_LEFT_ALIGN_BOTTOM:
271       rect.y -= rect.height - target_rect->height;
272       /* fall-through */
273     case POSITION_LEFT_ALIGN_TOP:
274       rect.x -= rect.width;
275       break;
276   }
277 
278   *position_rect = rect;
279 
280   return rect.x >= bounding_rect->x &&
281          rect.y >= bounding_rect->y &&
282          rect.x + rect.width <= bounding_rect->x + bounding_rect->width &&
283          rect.y + rect.height <= bounding_rect->y + bounding_rect->height;
284 }
285 
286 static gboolean
position_rectangle_on_screen(GtkWidget * widget,GdkRectangle * rectangle,GdkRectangle * target_rect)287 position_rectangle_on_screen (GtkWidget *widget,
288                               GdkRectangle *rectangle,
289                               GdkRectangle *target_rect)
290 {
291   GtkTextDirection direction;
292   GdkRectangle monitor;
293   int monitor_num;
294   GdkScreen *screen;
295   static const PositionType positions[] = {
296     POSITION_DOWN_ALIGN_LEFT,
297     POSITION_TOP_ALIGN_LEFT,
298     POSITION_RIGHT_ALIGN_TOP,
299     POSITION_LEFT_ALIGN_TOP,
300     POSITION_DOWN_ALIGN_RIGHT,
301     POSITION_TOP_ALIGN_RIGHT,
302     POSITION_RIGHT_ALIGN_BOTTOM,
303     POSITION_LEFT_ALIGN_BOTTOM
304   };
305   guint i;
306 
307   direction = gtk_widget_get_direction (widget);
308   screen = gtk_widget_get_screen (widget);
309   monitor_num = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (widget));
310   if (monitor_num < 0)
311     monitor_num = 0;
312 #if GTK_CHECK_VERSION (3, 3, 5)
313   gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor);
314 #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       return TRUE;
321   }
322 
323   return FALSE;
324 }
325 
326 static void
get_root_coords_at_active_char(GucharmapChartable * chartable,gint * x_root,gint * y_root)327 get_root_coords_at_active_char (GucharmapChartable *chartable,
328                                 gint *x_root,
329                                 gint *y_root)
330 {
331   GucharmapChartablePrivate *priv = chartable->priv;
332   GtkWidget *widget = GTK_WIDGET (chartable);
333   gint x, y;
334   gint row, col;
335 
336   gdk_window_get_origin (gtk_widget_get_window (widget), &x, &y);
337 
338   row = (priv->active_cell - priv->page_first_cell) / priv->cols;
339   col = _gucharmap_chartable_cell_column (chartable, priv->active_cell);
340 
341   *x_root = x + _gucharmap_chartable_x_offset (chartable, col);
342   *y_root = y + _gucharmap_chartable_y_offset (chartable, row);
343 }
344 
345 static void
get_active_cell_rect(GucharmapChartable * chartable,GdkRectangle * rect)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   row = (priv->active_cell - priv->page_first_cell) / priv->cols;
354   col = _gucharmap_chartable_cell_column (chartable, priv->active_cell);
355 
356   rect->width = _gucharmap_chartable_column_width (chartable, col);
357   rect->height = _gucharmap_chartable_row_height (chartable, row);
358 }
359 
360 static void
get_appropriate_upper_left_xy(GucharmapChartable * chartable,gint width,gint height,gint x_root,gint y_root,gint * x,gint * y)361 get_appropriate_upper_left_xy (GucharmapChartable *chartable,
362                                gint width,  gint height,
363                                gint x_root, gint y_root,
364                                gint *x,     gint *y)
365 {
366   GucharmapChartablePrivate *priv = chartable->priv;
367   gint row, col;
368 
369   row = (priv->active_cell - priv->page_first_cell) / priv->cols;
370   col = _gucharmap_chartable_cell_column (chartable, priv->active_cell);
371 
372   *x = x_root;
373   *y = y_root;
374 
375   if (row >= priv->rows / 2)
376     *y -= height;
377 
378   if (col >= priv->cols / 2)
379     *x -= width;
380 }
381 
382 /* depends on directionality */
383 static guint
get_cell_at_rowcol(GucharmapChartable * chartable,gint row,gint col)384 get_cell_at_rowcol (GucharmapChartable *chartable,
385                     gint            row,
386                     gint            col)
387 {
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 /* Depends on directionality. Column 0 is the furthest left.  */
398 gint
_gucharmap_chartable_cell_column(GucharmapChartable * chartable,guint cell)399 _gucharmap_chartable_cell_column (GucharmapChartable *chartable,
400                               guint cell)
401 {
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 /* not all columns are necessarily the same width because of padding */
412 gint
_gucharmap_chartable_column_width(GucharmapChartable * chartable,gint col)413 _gucharmap_chartable_column_width (GucharmapChartable *chartable, gint col)
414 {
415   GucharmapChartablePrivate *priv = chartable->priv;
416   int num_padded_columns = priv->n_padded_columns;
417   int min_col_w = priv->minimal_column_width;
418 
419   if (priv->cols - col <= num_padded_columns)
420     return min_col_w + 1;
421   else
422     return min_col_w;
423 }
424 
425 /* calculates the position of the left end of the column (just to the right
426  * of the left border) */
427 /* XXX: calling this repeatedly is not the most efficient, but it probably
428  * is the most readable */
429 gint
_gucharmap_chartable_x_offset(GucharmapChartable * chartable,gint col)430 _gucharmap_chartable_x_offset (GucharmapChartable *chartable, gint col)
431 {
432   gint c, x;
433 
434   for (c = 0, x = 1;  c < col;  c++)
435     x += _gucharmap_chartable_column_width (chartable, c);
436 
437   return x;
438 }
439 
440 /* not all rows are necessarily the same height because of padding */
441 gint
_gucharmap_chartable_row_height(GucharmapChartable * chartable,gint row)442 _gucharmap_chartable_row_height (GucharmapChartable *chartable, gint row)
443 {
444   GucharmapChartablePrivate *priv = chartable->priv;
445   int num_padded_rows = priv->n_padded_rows;
446   int min_row_h = priv->minimal_row_height;
447 
448   if (priv->rows - row <= num_padded_rows)
449     return min_row_h + 1;
450   else
451     return min_row_h;
452 }
453 
454 /* calculates the position of the top end of the row (just below the top
455  * border) */
456 /* XXX: calling this repeatedly is not the most efficient, but it probably
457  * is the most readable */
458 gint
_gucharmap_chartable_y_offset(GucharmapChartable * chartable,gint row)459 _gucharmap_chartable_y_offset (GucharmapChartable *chartable, gint row)
460 {
461   gint r, y;
462 
463   for (r = 0, y = 1;  r < row;  r++)
464     y += _gucharmap_chartable_row_height (chartable, r);
465 
466   return y;
467 }
468 
469 /* returns the font family of the last glyph item in the first line of the
470  * layout; should be freed by caller */
471 static gchar *
get_font(PangoLayout * layout)472 get_font (PangoLayout *layout)
473 {
474   PangoLayoutLine *line;
475   PangoGlyphItem *glyph_item;
476   PangoFont *font;
477   GSList *run_node;
478   gchar *family;
479   PangoFontDescription *font_desc;
480 
481   line = pango_layout_get_line (layout, 0);
482 
483   /* get to the last glyph_item (the one with the character we're drawing */
484   for (run_node = line->runs;
485        run_node && run_node->next;
486        run_node = run_node->next);
487 
488   if (run_node)
489     {
490       glyph_item = run_node->data;
491       font = glyph_item->item->analysis.font;
492       font_desc = pango_font_describe (font);
493 
494       family = g_strdup (pango_font_description_get_family (font_desc));
495 
496       pango_font_description_free (font_desc);
497     }
498   else
499     family = NULL;
500 
501   return family;
502 }
503 
504 /* font_family (if not null) gets filled in with the actual font family
505  * used to draw the character */
506 static PangoLayout *
layout_scaled_glyph(GucharmapChartable * chartable,gunichar uc,double font_factor,char ** font_family)507 layout_scaled_glyph (GucharmapChartable *chartable,
508                      gunichar uc,
509                      double font_factor,
510                      char **font_family)
511 {
512   GucharmapChartablePrivate *priv = chartable->priv;
513   PangoFontDescription *font_desc;
514   PangoLayout *layout;
515   gchar buf[11];
516 
517   font_desc = pango_font_description_copy (priv->font_desc);
518 
519   if (pango_font_description_get_size_is_absolute (priv->font_desc))
520     pango_font_description_set_absolute_size (font_desc,
521                                               font_factor * pango_font_description_get_size (priv->font_desc));
522   else
523     pango_font_description_set_size (font_desc,
524                                      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   pango_layout_set_font_description (layout, font_desc);
530 
531   buf[gucharmap_unichar_to_printable_utf8 (uc, buf)] = '\0';
532   pango_layout_set_text (layout, buf, -1);
533 
534   if (priv->font_fallback == FALSE)
535     {
536       PangoAttrList *list;
537 
538       list = pango_attr_list_new ();
539       pango_attr_list_insert (list, pango_attr_fallback_new (FALSE));
540       pango_layout_set_attributes (layout, list);
541       pango_attr_list_unref (list);
542     }
543 
544   if (font_family)
545     *font_family = get_font (layout);
546 
547   pango_font_description_free (font_desc);
548 
549   return layout;
550 }
551 
552 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)553 create_glyph_surface (GucharmapChartable *chartable,
554                       gunichar wc,
555                       double font_factor,
556                       gboolean draw_font_family,
557                       int *zoom_surface_width,
558                       int *zoom_surface_height)
559 {
560   GtkWidget *widget = GTK_WIDGET (chartable);
561   enum { PADDING = 8 };
562 
563   PangoLayout *pango_layout, *pango_layout2 = NULL;
564   PangoRectangle char_rect, family_rect;
565   gint width, height;
566   GtkStyle *style;
567   char *family;
568   cairo_surface_t *surface;
569   cairo_t *cr;
570 
571   /* Apply the scaling.  Unfortunately not all fonts seem to be scalable.
572    * We could fall back to GdkPixbuf scaling, but that looks butt ugly :-/
573    */
574   pango_layout = layout_scaled_glyph (chartable, wc,
575                                       font_factor, &family);
576   pango_layout_get_pixel_extents (pango_layout, &char_rect, NULL);
577 
578   if (draw_font_family)
579     {
580       if (family == NULL)
581         family = g_strdup (_("[not a printable character]"));
582 
583       pango_layout2 = gtk_widget_create_pango_layout (GTK_WIDGET (chartable), family);
584       pango_layout_get_pixel_extents (pango_layout2, NULL, &family_rect);
585 
586       /* Make the GdkPixmap large enough to account for possible offsets in the
587        * ink extents of the glyph. */
588       width  = MAX (char_rect.width, family_rect.width)  + 2 * PADDING;
589       height = family_rect.height + char_rect.height + 4 * PADDING;
590     }
591   else
592     {
593       width  = char_rect.width + 2 * PADDING;
594       height = char_rect.height + 2 * PADDING;
595     }
596 
597   style = gtk_widget_get_style (widget);
598 
599   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
600                                                CAIRO_CONTENT_COLOR,
601                                                width, height);
602   cr = cairo_create (surface);
603 
604   gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
605   cairo_rectangle (cr, 0, 0, width, height);
606   cairo_fill (cr);
607 
608   gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_INSENSITIVE]);
609   cairo_set_line_width (cr, 1);
610   cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
611   cairo_rectangle (cr, 1.5, 1.5, width - 3, height - 3);
612   cairo_stroke (cr);
613 
614   /* Now draw the glyph.  The coordinates are adapted
615    * in order to compensate negative char_rect offsets.
616    */
617   gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
618   cairo_move_to (cr, -char_rect.x + PADDING, -char_rect.y + PADDING);
619   pango_cairo_show_layout (cr, pango_layout);
620   g_object_unref (pango_layout);
621 
622   if (draw_font_family)
623     {
624       cairo_set_line_width (cr, 1);
625       cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
626       gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
627       cairo_move_to (cr, 6 + 1 + .5, char_rect.height + 2 * PADDING + .5);
628       cairo_line_to (cr, width - 3 - 6 - .5, char_rect.height + 2 * PADDING + .5);
629       cairo_stroke (cr);
630 
631       gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
632       cairo_move_to (cr, PADDING, height - PADDING - family_rect.height);
633       /* FIXME: clip here? */
634       pango_cairo_show_layout (cr, pango_layout2);
635 
636       g_object_unref (pango_layout2);
637     }
638 
639   g_free (family);
640 
641   cairo_destroy (cr);
642 
643   if (zoom_surface_width)
644     *zoom_surface_width = width;
645   if (zoom_surface_height)
646     *zoom_surface_height = height;
647 
648   return surface;
649 }
650 
651 /* places the zoom window toward the inside of the coordinates */
652 static void
place_zoom_window(GucharmapChartable * chartable,gint x_root,gint y_root)653 place_zoom_window (GucharmapChartable *chartable, gint x_root, gint y_root)
654 {
655   GucharmapChartablePrivate *priv = chartable->priv;
656   int x, y;
657 
658   if (!priv->zoom_window)
659     return;
660 
661   get_appropriate_upper_left_xy (chartable,
662                                  priv->zoom_image_width,
663                                  priv->zoom_image_height,
664                                  x_root, y_root, &x, &y);
665   gtk_window_move (GTK_WINDOW (priv->zoom_window), x, y);
666 }
667 
668 static void
place_zoom_window_on_active_cell(GucharmapChartable * chartable)669 place_zoom_window_on_active_cell (GucharmapChartable *chartable)
670 {
671   GucharmapChartablePrivate *priv = chartable->priv;
672   GdkRectangle rect, keepout_rect;
673 
674   if (!priv->zoom_window)
675     return;
676 
677   get_active_cell_rect (chartable, &keepout_rect);
678 
679   rect.x = rect.y = 0;
680   rect.width = priv->zoom_image_width;
681   rect.height = priv->zoom_image_height;
682 
683   position_rectangle_on_screen (GTK_WIDGET (chartable),
684                                 &rect,
685                                 &keepout_rect);
686   gtk_window_move (GTK_WINDOW (priv->zoom_window), rect.x, rect.y);
687 }
688 
689 static int
get_font_size_px(GucharmapChartable * chartable)690 get_font_size_px (GucharmapChartable *chartable)
691 {
692   GucharmapChartablePrivate *priv = chartable->priv;
693   GtkWidget *widget = GTK_WIDGET (chartable);
694   GdkScreen *screen;
695   double resolution;
696   int font_size;
697 
698   g_assert (priv->font_desc != NULL);
699 
700   screen = gtk_widget_get_screen (widget);
701   resolution = gdk_screen_get_resolution (screen);
702   if (resolution < 0.0) /* will be -1 if the resolution is not defined in the GdkScreen */
703     resolution = 96.0;
704 
705   if (pango_font_description_get_size_is_absolute (priv->font_desc))
706     font_size = pango_font_description_get_size (priv->font_desc);
707   else
708     font_size = ((double) pango_font_description_get_size (priv->font_desc)) * resolution / 72.0;
709 
710   if (PANGO_PIXELS (font_size) <= 0)
711     font_size = DEFAULT_FONT_SIZE * resolution / 72.0;
712 
713   return PANGO_PIXELS (font_size);
714 }
715 
716 static void
update_zoom_window(GucharmapChartable * chartable)717 update_zoom_window (GucharmapChartable *chartable)
718 {
719   GucharmapChartablePrivate *priv = chartable->priv;
720   GtkWidget *widget = GTK_WIDGET (chartable);
721   double scale;
722   int font_size_px, screen_height;
723   cairo_surface_t *surface;
724 
725   if (priv->zoom_window == NULL)
726     return;
727 
728   font_size_px = get_font_size_px (chartable);
729   screen_height = gdk_screen_get_height (gtk_widget_get_screen (widget));
730 
731   scale = (0.3 * screen_height) / (FACTOR_WIDTH * font_size_px);
732   scale = CLAMP (scale, 1.0, 12.0);
733 
734   surface = create_glyph_surface (chartable,
735                                   gucharmap_chartable_get_active_character (chartable),
736                                   scale, TRUE,
737                                   &priv->zoom_image_width,
738                                   &priv->zoom_image_height);
739   gtk_image_set_from_surface (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (priv->zoom_window))),
740                               surface);
741   cairo_surface_destroy (surface);
742 
743   gtk_window_resize (GTK_WINDOW (priv->zoom_window),
744                      priv->zoom_image_width, priv->zoom_image_height);
745 }
746 
747 static void
make_zoom_window(GucharmapChartable * chartable)748 make_zoom_window (GucharmapChartable *chartable)
749 {
750   GucharmapChartablePrivate *priv = chartable->priv;
751   GtkWidget *widget = GTK_WIDGET (chartable);
752   GtkWidget *image;
753 
754   /* if there is already a zoom window, do nothing */
755   if (priv->zoom_window || !priv->zoom_mode_enabled)
756     return;
757 
758   priv->zoom_window = gtk_window_new (GTK_WINDOW_POPUP);
759   /* For wayland, we need to "attach" the popup to the toplevel */
760   gtk_window_set_transient_for (GTK_WINDOW (priv->zoom_window),
761                                 GTK_WINDOW (gtk_widget_get_toplevel (widget)));
762   gtk_window_set_resizable (GTK_WINDOW (priv->zoom_window), FALSE);
763   gtk_window_set_screen (GTK_WINDOW (priv->zoom_window),
764                          gtk_widget_get_screen (widget));
765 
766   image = gtk_image_new ();
767   gtk_container_add (GTK_CONTAINER (priv->zoom_window), image);
768   gtk_widget_show (image);
769 }
770 
771 static void
destroy_zoom_window(GucharmapChartable * chartable)772 destroy_zoom_window (GucharmapChartable *chartable)
773 {
774   GucharmapChartablePrivate *priv = chartable->priv;
775 
776   if (priv->zoom_window)
777     {
778       GtkWidget *widget = GTK_WIDGET (chartable);
779       GtkWidget *zoom_window;
780 
781       zoom_window = priv->zoom_window;
782       priv->zoom_window = NULL;
783 
784       gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
785       gtk_widget_destroy (zoom_window);
786     }
787 }
788 
789 static void
gucharmap_chartable_show_zoom(GucharmapChartable * chartable)790 gucharmap_chartable_show_zoom (GucharmapChartable *chartable)
791 {
792   GucharmapChartablePrivate *priv = chartable->priv;
793 
794   if (!priv->zoom_mode_enabled)
795     return;
796 
797   make_zoom_window (chartable);
798   update_zoom_window (chartable);
799 
800   place_zoom_window_on_active_cell (chartable);
801 
802   gtk_widget_show (priv->zoom_window);
803 
804   g_object_notify (G_OBJECT (chartable), "zoom-showing");
805 }
806 
807 static void
gucharmap_chartable_hide_zoom(GucharmapChartable * chartable)808 gucharmap_chartable_hide_zoom (GucharmapChartable *chartable)
809 {
810   destroy_zoom_window (chartable);
811 
812   g_object_notify (G_OBJECT (chartable), "zoom-showing");
813 }
814 
815 static gunichar
get_cell_at_xy(GucharmapChartable * chartable,gint x,gint y)816 get_cell_at_xy (GucharmapChartable *chartable,
817                 gint            x,
818                 gint            y)
819 {
820   GucharmapChartablePrivate *priv = chartable->priv;
821   gint r, c, x0, y0;
822   guint cell;
823 
824   for (c = 0, x0 = 0;  x0 <= x && c < priv->cols;  c++)
825     x0 += _gucharmap_chartable_column_width (chartable, c);
826 
827   for (r = 0, y0 = 0;  y0 <= y && r < priv->rows;  r++)
828     y0 += _gucharmap_chartable_row_height (chartable, r);
829 
830   /* cell = rowcol_to_unichar (chartable, r-1, c-1); */
831   cell = get_cell_at_rowcol (chartable, r-1, c-1);
832 
833   /* XXX: check this somewhere else? */
834   if (cell > priv->last_cell)
835     return priv->last_cell;
836 
837   return cell;
838 }
839 
840 static void
draw_character(GucharmapChartable * chartable,cairo_t * cr,cairo_rectangle_int_t * rect,gint row,gint col)841 draw_character (GucharmapChartable *chartable,
842                 cairo_t            *cr,
843                 cairo_rectangle_int_t  *rect,
844                 gint            row,
845                 gint            col)
846 {
847   GucharmapChartablePrivate *priv = chartable->priv;
848   GtkWidget *widget = GTK_WIDGET (chartable);
849   int n, char_width, char_height;
850   gunichar wc;
851   guint cell;
852   GtkStyle *style;
853   GdkColor *color;
854   gchar buf[10];
855 
856   cell = get_cell_at_rowcol (chartable, row, col);
857   wc = gucharmap_codepoint_list_get_char (priv->codepoint_list, cell);
858 
859   if (wc > UNICHAR_MAX ||
860       !gucharmap_unichar_validate (wc) ||
861       !gucharmap_unichar_isdefined (wc))
862     return;
863 
864   n = gucharmap_unichar_to_printable_utf8 (wc, buf);
865   pango_layout_set_text (priv->pango_layout, buf, n);
866 
867   /* Keep the square empty if font fallback is disabled and the
868    * font has no glyph for this cell.
869    */
870   if (!priv->font_fallback &&
871       pango_layout_get_unknown_glyphs_count (priv->pango_layout) > 0)
872     return;
873 
874   cairo_save (cr);
875 
876   style = gtk_widget_get_style (widget);
877 
878   if (gtk_widget_has_focus (widget) && (gint)cell == priv->active_cell)
879     color = &style->text[GTK_STATE_SELECTED];
880   else if ((gint)cell == priv->active_cell)
881     color = &style->text[GTK_STATE_ACTIVE];
882   else
883     color = &style->text[GTK_STATE_NORMAL];
884 
885   gdk_cairo_set_source_color (cr, color);
886 
887   cairo_rectangle (cr,
888                    rect->x + 1, rect->y + 1,
889                    rect->width - 2, rect->height - 2);
890   cairo_clip (cr);
891 
892   pango_layout_get_pixel_size (priv->pango_layout, &char_width, &char_height);
893   cairo_move_to (cr,
894                  rect->x + (rect->width - char_width - 2 + 1) / 2,
895                  rect->y + (rect->height - char_height - 2 + 1) / 2);
896   pango_cairo_show_layout (cr, priv->pango_layout);
897 
898   cairo_restore (cr);
899 }
900 
901 static void
expose_square(GucharmapChartable * chartable,gint row,gint col)902 expose_square (GucharmapChartable *chartable, gint row, gint col)
903 {
904   GtkWidget *widget = GTK_WIDGET (chartable);
905 
906   gtk_widget_queue_draw_area (widget,
907                               _gucharmap_chartable_x_offset (chartable, col),
908                               _gucharmap_chartable_y_offset (chartable, row),
909                               _gucharmap_chartable_column_width (chartable, col) - 1,
910                               _gucharmap_chartable_row_height (chartable, row) - 1);
911 }
912 
913 static void
expose_cell(GucharmapChartable * chartable,guint cell)914 expose_cell (GucharmapChartable *chartable,
915              guint cell)
916 {
917   GucharmapChartablePrivate *priv = chartable->priv;
918 
919   gint row = (cell - priv->page_first_cell) / priv->cols;
920   gint col = _gucharmap_chartable_cell_column (chartable, cell);
921 
922   if (row >= 0 && row < priv->rows && col >= 0 && col < priv->cols)
923     expose_square (chartable, row, col);
924 }
925 
926 static void
draw_square_bg(GucharmapChartable * chartable,cairo_t * cr,cairo_rectangle_int_t * rect,gint row,gint col)927 draw_square_bg (GucharmapChartable *chartable,
928                 cairo_t *cr,
929                 cairo_rectangle_int_t  *rect,
930                 gint row,
931                 gint col)
932 {
933   GucharmapChartablePrivate *priv = chartable->priv;
934   GtkWidget *widget = GTK_WIDGET (chartable);
935   GdkColor *untinted;
936   GtkStyle *style;
937   guint cell;
938   gunichar wc;
939 
940   cairo_save (cr);
941 
942   cell = get_cell_at_rowcol (chartable, row, col);
943   wc = gucharmap_codepoint_list_get_char (priv->codepoint_list, cell);
944 
945   style = gtk_widget_get_style (widget);
946 
947   if (gtk_widget_has_focus (widget) && (gint)cell == priv->active_cell)
948     untinted = &style->base[GTK_STATE_SELECTED];
949   else if ((gint)cell == priv->active_cell)
950     untinted = &style->base[GTK_STATE_ACTIVE];
951   else if ((gint)cell > priv->last_cell)
952     untinted = &style->dark[GTK_STATE_NORMAL];
953   else if (! gucharmap_unichar_validate (wc))
954     untinted = &style->fg[GTK_STATE_INSENSITIVE];
955   else if (! gucharmap_unichar_isdefined (wc))
956     untinted = &style->bg[GTK_STATE_INSENSITIVE];
957   else
958     untinted = &style->base[GTK_STATE_NORMAL];
959 
960   gdk_cairo_set_source_color (cr, untinted);
961   cairo_set_line_width (cr, 1);
962   cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
963 
964   cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
965   cairo_fill (cr);
966 
967   cairo_restore (cr);
968 }
969 
970 static void
draw_borders(GucharmapChartable * chartable,cairo_t * cr)971 draw_borders (GucharmapChartable *chartable,
972               cairo_t *cr)
973 {
974   GucharmapChartablePrivate *priv = chartable->priv;
975   GtkWidget *widget = GTK_WIDGET (chartable);
976   GtkAllocation *allocation;
977   GtkStyle *style;
978   gint x, y, col, row;
979   GtkAllocation widget_allocation;
980 
981   gtk_widget_get_allocation (widget, &widget_allocation);
982   allocation = &widget_allocation;
983 
984   cairo_save (cr);
985 
986   /* dark_gc[GTK_STATE_NORMAL] seems to be what is used to draw the borders
987    * around widgets, so we use it for the lines */
988 
989   style = gtk_widget_get_style (widget);
990   gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
991 
992   cairo_set_line_width (cr, 1); /* FIXME themeable? */
993   cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
994 
995   /* vertical lines */
996   cairo_move_to (cr, .5, .5);
997   cairo_line_to (cr, .5, allocation->height - .5);
998 
999   for (col = 0, x = 0;  col < priv->cols;  col++)
1000     {
1001       x += _gucharmap_chartable_column_width (chartable, col);
1002       cairo_move_to (cr, x + .5, .5);
1003       cairo_line_to (cr, x + .5, allocation->height - .5);
1004     }
1005 
1006   /* horizontal lines */
1007   cairo_move_to (cr, .5, .5);
1008   cairo_line_to (cr, allocation->width - .5, .5);
1009 
1010   for (row = 0, y = 0;  row < priv->rows;  row++)
1011     {
1012       y += _gucharmap_chartable_row_height (chartable, row);
1013 
1014       cairo_move_to (cr, .5, y + .5);
1015       cairo_line_to (cr, allocation->width - .5, y + .5);
1016     }
1017 
1018   cairo_stroke (cr);
1019   cairo_restore (cr);
1020 }
1021 
1022 static void
update_scrollbar_adjustment(GucharmapChartable * chartable)1023 update_scrollbar_adjustment (GucharmapChartable *chartable)
1024 {
1025   GucharmapChartablePrivate *priv = chartable->priv;
1026   GtkAdjustment *vadjustment = priv->vadjustment;
1027 
1028   if (!vadjustment)
1029     return;
1030 
1031   gtk_adjustment_configure (vadjustment,
1032                             priv->page_first_cell / priv->cols,
1033                             0 /* lower */,
1034                             priv->last_cell / priv->cols + 1 /* upper */,
1035                             3 /* step increment */,
1036                             priv->rows /* page increment */,
1037                             priv->rows);
1038 }
1039 
1040 static void
gucharmap_chartable_set_active_cell(GucharmapChartable * chartable,int cell)1041 gucharmap_chartable_set_active_cell (GucharmapChartable *chartable,
1042                                      int cell)
1043 {
1044   GtkWidget *widget = GTK_WIDGET (chartable);
1045   GucharmapChartablePrivate *priv = chartable->priv;
1046   int old_active_cell, old_page_first_cell;
1047 
1048   if (cell == priv->active_cell)
1049     return;
1050 
1051   if (cell < 0)
1052     cell = 0;
1053   else if (cell > priv->last_cell)
1054     cell = priv->last_cell;
1055 
1056   old_active_cell = priv->active_cell;
1057   old_page_first_cell = priv->page_first_cell;
1058 
1059   priv->active_cell = cell;
1060 
1061   if (cell < priv->page_first_cell || cell >= priv->page_first_cell + priv->page_size)
1062     {
1063       int old_row = old_active_cell / priv->cols;
1064       int new_row = cell / priv->cols;
1065       int new_page_first_cell = old_page_first_cell + (new_row - old_row) * priv->cols;
1066       int last_page_first_cell = (priv->last_cell / priv->cols - priv->rows + 1) * priv->cols;
1067 
1068       priv->page_first_cell = CLAMP (new_page_first_cell, 0, last_page_first_cell);
1069 
1070       if (priv->vadjustment)
1071         gtk_adjustment_set_value (priv->vadjustment, priv->page_first_cell / priv->cols);
1072     }
1073   else if (gtk_widget_get_realized (widget)) {
1074     expose_cell (chartable, old_active_cell);
1075     expose_cell (chartable, cell);
1076   }
1077 
1078   g_object_notify (G_OBJECT (chartable), "active-character");
1079 
1080   update_zoom_window (chartable);
1081   place_zoom_window_on_active_cell (chartable);
1082 }
1083 
1084 static void
set_active_char(GucharmapChartable * chartable,gunichar wc)1085 set_active_char (GucharmapChartable *chartable,
1086                  gunichar        wc)
1087 {
1088   GucharmapChartablePrivate *priv = chartable->priv;
1089 
1090   guint cell = gucharmap_codepoint_list_get_index (priv->codepoint_list, wc);
1091   if (cell == -1) {
1092     gtk_widget_error_bell (GTK_WIDGET (chartable));
1093     return;
1094   }
1095 
1096   gucharmap_chartable_set_active_cell (chartable, cell);
1097 }
1098 
1099 static void
vadjustment_value_changed_cb(GtkAdjustment * vadjustment,GucharmapChartable * chartable)1100 vadjustment_value_changed_cb (GtkAdjustment *vadjustment,
1101                               GucharmapChartable *chartable)
1102 {
1103   GucharmapChartablePrivate *priv = chartable->priv;
1104   int row, r, c, old_page_first_cell, old_active_cell, first_cell;
1105 
1106   row = (int) gtk_adjustment_get_value (vadjustment);
1107 
1108   if (row < 0 || row > priv->last_cell / priv->cols)
1109     row = 0;
1110 
1111   first_cell = row * priv->cols;
1112 
1113   gtk_widget_queue_draw (GTK_WIDGET (chartable));
1114 
1115   old_page_first_cell = priv->page_first_cell;
1116   old_active_cell = priv->active_cell;
1117 
1118   priv->page_first_cell = first_cell;
1119 
1120   /* character is still on the visible page */
1121   if (priv->active_cell - priv->page_first_cell >= 0
1122       && priv->active_cell - priv->page_first_cell < priv->page_size)
1123     return;
1124 
1125   c = old_active_cell % priv->cols;
1126 
1127   if (priv->page_first_cell < old_page_first_cell)
1128     r = priv->rows - 1;
1129   else
1130     r = 0;
1131 
1132   gucharmap_chartable_set_active_cell (chartable, priv->page_first_cell + r * priv->cols + c);
1133 }
1134 
1135 /* GtkWidget class methods */
1136 
1137 /*  - single click with left button: activate character under pointer
1138  *  - double-click with left button: add active character to text_to_copy
1139  *  - single-click with middle button: jump to selection_primary
1140  */
1141 static gboolean
gucharmap_chartable_button_press(GtkWidget * widget,GdkEventButton * event)1142 gucharmap_chartable_button_press (GtkWidget *widget,
1143                                   GdkEventButton *event)
1144 {
1145   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1146   GucharmapChartablePrivate *priv = chartable->priv;
1147 
1148   /* in case we lost keyboard focus and are clicking to get it back */
1149   gtk_widget_grab_focus (widget);
1150 
1151   if (event->button == 1)
1152     {
1153       priv->click_x = event->x;
1154       priv->click_y = event->y;
1155     }
1156 
1157   /* double-click */
1158   if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1159     {
1160       g_signal_emit (chartable, signals[ACTIVATE], 0);
1161     }
1162   /* single-click */
1163   else if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
1164     {
1165       gucharmap_chartable_set_active_cell (chartable, get_cell_at_xy (chartable, event->x, event->y));
1166     }
1167   else if (event->button == 3)
1168     {
1169       gucharmap_chartable_set_active_cell (chartable, get_cell_at_xy (chartable, event->x, event->y));
1170       gucharmap_chartable_show_zoom (chartable);
1171     }
1172 
1173   /* XXX: [need to return false so it gets drag events] */
1174   /* actually return true because we handle drag_begin because of
1175    * http://bugzilla.gnome.org/show_bug.cgi?id=114534 */
1176   return TRUE;
1177 }
1178 
1179 static gboolean
gucharmap_chartable_button_release(GtkWidget * widget,GdkEventButton * event)1180 gucharmap_chartable_button_release (GtkWidget *widget,
1181                                     GdkEventButton *event)
1182 {
1183   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1184   gboolean (* button_press_event) (GtkWidget *, GdkEventButton *) =
1185     GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->button_release_event;
1186 
1187   if (event->button == 3)
1188     gucharmap_chartable_hide_zoom (chartable);
1189 
1190   if (button_press_event)
1191     return button_press_event (widget, event);
1192   return FALSE;
1193 }
1194 
1195 static void
gucharmap_chartable_drag_begin(GtkWidget * widget,GdkDragContext * context)1196 gucharmap_chartable_drag_begin (GtkWidget *widget,
1197                                 GdkDragContext *context)
1198 {
1199   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1200   double scale;
1201   int font_size_px, screen_height;
1202   cairo_surface_t *drag_surface;
1203 
1204   font_size_px = get_font_size_px (chartable);
1205   screen_height = gdk_screen_get_height (gtk_widget_get_screen (widget));
1206 
1207   scale = (0.3 * screen_height) / (FACTOR_WIDTH * font_size_px);
1208   scale = CLAMP (scale, 1.0, 5.0);
1209 
1210   drag_surface = create_glyph_surface (chartable,
1211                                        gucharmap_chartable_get_active_character (chartable),
1212                                        scale,
1213                                        FALSE, NULL, NULL);
1214   gtk_drag_set_icon_surface (context, drag_surface);
1215   cairo_surface_destroy (drag_surface);
1216 
1217   /* no need to chain up */
1218 }
1219 
1220 static void
gucharmap_chartable_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)1221 gucharmap_chartable_drag_data_get (GtkWidget *widget,
1222                                    GdkDragContext *context,
1223                                    GtkSelectionData *selection_data,
1224                                    guint info,
1225                                    guint time)
1226 
1227 {
1228   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1229   GucharmapChartablePrivate *priv = chartable->priv;
1230   gchar buf[7];
1231   gint n;
1232 
1233   n = g_unichar_to_utf8 (gucharmap_codepoint_list_get_char (priv->codepoint_list, priv->active_cell), buf);
1234   gtk_selection_data_set_text (selection_data, buf, n);
1235 
1236   /* no need to chain up */
1237 }
1238 
1239 static void
gucharmap_chartable_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time)1240 gucharmap_chartable_drag_data_received (GtkWidget *widget,
1241                                         GdkDragContext *context,
1242                                         gint x, gint y,
1243                                         GtkSelectionData *selection_data,
1244                                         guint info,
1245                                         guint time)
1246 {
1247   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1248   GucharmapChartablePrivate *priv = chartable->priv;
1249   gchar *text;
1250   gunichar wc;
1251 
1252   if (gtk_selection_data_get_length (selection_data) <= 0 ||
1253       gtk_selection_data_get_data (selection_data) == NULL)
1254     return;
1255 
1256   text = (gchar *) gtk_selection_data_get_text (selection_data);
1257 
1258   if (text == NULL) /* XXX: say something in the statusbar? */
1259     return;
1260 
1261   wc = g_utf8_get_char_validated (text, -1);
1262 
1263   if (wc == (gunichar)(-2) || wc == (gunichar)(-1) || wc > UNICHAR_MAX)
1264     gucharmap_chartable_emit_status_message (chartable, _("Unknown character, unable to identify."));
1265   else if (gucharmap_codepoint_list_get_index (priv->codepoint_list, wc) == (guint)(-1))
1266     gucharmap_chartable_emit_status_message (chartable, _("Not found."));
1267   else
1268     {
1269       gucharmap_chartable_emit_status_message (chartable, _("Character found."));
1270       set_active_char (chartable, wc);
1271       place_zoom_window_on_active_cell (chartable);
1272     }
1273 
1274   g_free (text);
1275 
1276   /* no need to chain up */
1277 }
1278 
1279 static gboolean
gucharmap_chartable_draw(GtkWidget * widget,cairo_t * cr)1280 gucharmap_chartable_draw (GtkWidget *widget,
1281                           cairo_t *cr)
1282 {
1283   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1284   GucharmapChartablePrivate *priv = chartable->priv;
1285   GtkStyle *style;
1286   int row, col;
1287   cairo_rectangle_int_t clip_rect;
1288   cairo_region_t *region;
1289 
1290   if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
1291     return FALSE;
1292 
1293   region = cairo_region_create_rectangle (&clip_rect);
1294 
1295   if (cairo_region_is_empty (region)) {
1296     cairo_region_destroy (region);
1297     return FALSE;
1298   }
1299 
1300 #if 0
1301   {
1302     int i, n_rects;
1303 
1304     n_rects = cairo_region_num_rectangles (event->region);
1305 
1306     g_print ("Exposing area %d:%d@(%d,%d) with %d rects ", event->area.width, event->area.height,
1307              event->area.x, event->area.y, n_rects);
1308     for (i = 0; i < n_rects; ++i) {
1309       g_print ("[Rect %d:%d@(%d,%d)] ", rects[i].width, rects[i].height, rects[i].x, rects[i].y);
1310     }
1311     g_print ("\n");
1312   }
1313 #endif
1314 
1315   style = gtk_widget_get_style (widget);
1316   gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
1317   gdk_cairo_region (cr, region);
1318   cairo_fill (cr);
1319 
1320   if (priv->codepoint_list == NULL)
1321     goto expose_done;
1322 
1323   gucharmap_chartable_ensure_pango_layout (chartable);
1324 
1325   for (row = priv->rows - 1; row >= 0; --row)
1326     {
1327       for (col = priv->cols - 1; col >= 0; --col)
1328         {
1329           cairo_rectangle_int_t rect;
1330 
1331           rect.x = _gucharmap_chartable_x_offset (chartable, col);
1332           rect.y = _gucharmap_chartable_y_offset (chartable, row);
1333           rect.width = _gucharmap_chartable_column_width (chartable, col);
1334           rect.height = _gucharmap_chartable_row_height (chartable, row);
1335 
1336           if (cairo_region_contains_rectangle (region, &rect) == CAIRO_REGION_OVERLAP_OUT)
1337             continue;
1338 
1339           draw_square_bg (chartable, cr, &rect, row, col);
1340           draw_character (chartable, cr, &rect, row, col);
1341         }
1342     }
1343 
1344   draw_borders (chartable, cr);
1345 
1346 expose_done:
1347 
1348   cairo_region_destroy (region);
1349 
1350   /* no need to chain up */
1351   return FALSE;
1352 }
1353 
1354 static gboolean
gucharmap_chartable_focus_in_event(GtkWidget * widget,GdkEventFocus * event)1355 gucharmap_chartable_focus_in_event (GtkWidget *widget,
1356                                     GdkEventFocus *event)
1357 {
1358   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1359   GucharmapChartablePrivate *priv = chartable->priv;
1360 
1361   expose_cell (chartable, priv->active_cell);
1362 
1363   return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->focus_in_event (widget, event);
1364 }
1365 
1366 static gboolean
gucharmap_chartable_focus_out_event(GtkWidget * widget,GdkEventFocus * event)1367 gucharmap_chartable_focus_out_event (GtkWidget *widget,
1368                                      GdkEventFocus *event)
1369 {
1370   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1371   GucharmapChartablePrivate *priv = chartable->priv;
1372 
1373   gucharmap_chartable_hide_zoom (chartable);
1374 
1375   expose_cell (chartable, priv->active_cell);
1376 
1377   /* FIXME: the parent's handler already does a draw... */
1378 
1379   return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->focus_out_event (widget, event);
1380 }
1381 
1382 static gboolean
gucharmap_chartable_key_press_event(GtkWidget * widget,GdkEventKey * event)1383 gucharmap_chartable_key_press_event (GtkWidget *widget,
1384                                      GdkEventKey *event)
1385 {
1386   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1387 
1388   if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK))
1389     return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->key_press_event (widget, event);
1390 
1391   switch (event->keyval)
1392     {
1393       case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
1394         gucharmap_chartable_show_zoom (chartable);
1395         break;
1396 
1397       /* pass on other keys, like tab and stuff that shifts focus */
1398       default:
1399         break;
1400     }
1401 
1402   return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->key_press_event (widget, event);
1403 }
1404 
1405 static gboolean
gucharmap_chartable_key_release_event(GtkWidget * widget,GdkEventKey * event)1406 gucharmap_chartable_key_release_event (GtkWidget *widget,
1407                                        GdkEventKey *event)
1408 {
1409   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1410 
1411   switch (event->keyval)
1412     {
1413       /* XXX: If the group(shift_toggle) Xkb option is set, then releasing
1414        * the shift key gives either ISO_Next_Group or ISO_Prev_Group. Is
1415        * there a better way to handle this case? */
1416       case GDK_KEY_Shift_L:
1417       case GDK_KEY_Shift_R:
1418       case GDK_KEY_ISO_Next_Group:
1419       case GDK_KEY_ISO_Prev_Group:
1420         gucharmap_chartable_hide_zoom (chartable);
1421         break;
1422     }
1423 
1424   return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->key_release_event (widget, event);
1425 }
1426 
1427 static gboolean
gucharmap_chartable_motion_notify(GtkWidget * widget,GdkEventMotion * event)1428 gucharmap_chartable_motion_notify (GtkWidget *widget,
1429                                    GdkEventMotion *event)
1430 {
1431   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1432   GucharmapChartablePrivate *priv = chartable->priv;
1433   gboolean (* motion_notify_event) (GtkWidget *, GdkEventMotion *) =
1434     GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->motion_notify_event;
1435 
1436   if ((event->state & GDK_BUTTON1_MASK) &&
1437       gtk_drag_check_threshold (widget,
1438                                 priv->click_x,
1439                                 priv->click_y,
1440                                 event->x,
1441                                 event->y) &&
1442       gucharmap_unichar_validate (gucharmap_chartable_get_active_character (chartable)))
1443     {
1444       gtk_drag_begin (widget, priv->target_list,
1445                       GDK_ACTION_COPY, 1, (GdkEvent *) event);
1446     }
1447 
1448   if ((event->state & GDK_BUTTON3_MASK) != 0 &&
1449       priv->zoom_window)
1450     {
1451       guint cell = get_cell_at_xy (chartable, MAX (0, event->x), MAX (0, event->y));
1452 
1453       if ((gint)cell != priv->active_cell)
1454         {
1455           gtk_widget_hide (priv->zoom_window);
1456           gucharmap_chartable_set_active_cell (chartable, cell);
1457         }
1458 
1459       place_zoom_window (chartable, event->x_root, event->y_root);
1460       gtk_widget_show (priv->zoom_window);
1461     }
1462 
1463   if (motion_notify_event)
1464     motion_notify_event (widget, event);
1465   return FALSE;
1466 }
1467 
1468 #define FIRST_CELL_IN_SAME_ROW(x) ((x) - ((x) % priv->cols))
1469 
1470 static void
gucharmap_chartable_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1471 gucharmap_chartable_size_allocate (GtkWidget *widget,
1472                                    GtkAllocation *allocation)
1473 {
1474   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1475   GucharmapChartablePrivate *priv = chartable->priv;
1476   int old_rows, old_cols;
1477   int total_extra_pixels;
1478   int new_first_cell;
1479   int bare_minimal_column_width, bare_minimal_row_height;
1480   int font_size_px;
1481   GtkAllocation widget_allocation;
1482 
1483   GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->size_allocate (widget, allocation);
1484 
1485   gtk_widget_get_allocation (widget, &widget_allocation);
1486   allocation = &widget_allocation;
1487 
1488   old_rows = priv->rows;
1489   old_cols = priv->cols;
1490 
1491   font_size_px = get_font_size_px (chartable);
1492 
1493   /* FIXMEchpe bug 329481 */
1494   bare_minimal_column_width = FACTOR_WIDTH * font_size_px;
1495   bare_minimal_row_height = FACTOR_HEIGHT * font_size_px;
1496 
1497   if (priv->snap_pow2_enabled)
1498     priv->cols = (1 << g_bit_nth_msf ((allocation->width - 1) / bare_minimal_column_width, -1));
1499   else
1500     priv->cols = (allocation->width - 1) / bare_minimal_column_width;
1501 
1502   priv->rows = (allocation->height - 1) / bare_minimal_row_height;
1503 
1504   /* avoid a horrible floating point exception crash */
1505   if (priv->rows < 1)
1506     priv->rows = 1;
1507   if (priv->cols < 1)
1508     priv->cols = 1;
1509 
1510   priv->page_size = priv->rows * priv->cols;
1511 
1512   total_extra_pixels = allocation->width - (priv->cols * bare_minimal_column_width + 1);
1513   priv->minimal_column_width = bare_minimal_column_width + total_extra_pixels / priv->cols;
1514   priv->n_padded_columns = allocation->width - (priv->minimal_column_width * priv->cols + 1);
1515 
1516   total_extra_pixels = allocation->height - (priv->rows * bare_minimal_row_height + 1);
1517   priv->minimal_row_height = bare_minimal_row_height + total_extra_pixels / priv->rows;
1518   priv->n_padded_rows = allocation->height - (priv->minimal_row_height * priv->rows + 1);
1519 
1520   if (priv->rows == old_rows && priv->cols == old_cols)
1521     return;
1522 
1523   /* Need to recalculate the first cell, see bug #517188 */
1524   new_first_cell = FIRST_CELL_IN_SAME_ROW (priv->active_cell);
1525   if ((new_first_cell + priv->rows*priv->cols) > (priv->last_cell))
1526     {
1527       /* last cell is visible, so make sure it is in the last row */
1528       new_first_cell = FIRST_CELL_IN_SAME_ROW (priv->last_cell) - priv->page_size + priv->cols;
1529 
1530       if (new_first_cell < 0)
1531         new_first_cell = 0;
1532     }
1533   priv->page_first_cell = new_first_cell;
1534 
1535   update_scrollbar_adjustment (chartable);
1536 }
1537 
1538 static void
gucharmap_chartable_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1539 gucharmap_chartable_get_preferred_width (GtkWidget *widget,
1540                                          gint      *minimum,
1541                                          gint      *natural)
1542 {
1543   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1544   int font_size_px;
1545 
1546   font_size_px = get_font_size_px (chartable);
1547 
1548   *minimum = *natural = FACTOR_WIDTH * font_size_px;
1549 }
1550 
1551 static void
gucharmap_chartable_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1552 gucharmap_chartable_get_preferred_height (GtkWidget *widget,
1553                                           gint      *minimum,
1554                                           gint      *natural)
1555 {
1556   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1557   int font_size_px;
1558 
1559   font_size_px = get_font_size_px (chartable);
1560 
1561   *minimum = *natural = FACTOR_HEIGHT * font_size_px;
1562 }
1563 
1564 static void
gucharmap_chartable_style_set(GtkWidget * widget,GtkStyle * previous_style)1565 gucharmap_chartable_style_set (GtkWidget *widget,
1566                                GtkStyle *previous_style)
1567 {
1568   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (widget);
1569   GucharmapChartablePrivate *priv = chartable->priv;
1570 
1571   GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->style_set (widget, previous_style);
1572 
1573   gucharmap_chartable_clear_pango_layout (chartable);
1574 
1575   if (priv->font_desc == NULL) {
1576     GtkStyle *style;
1577     PangoFontDescription *font_desc;
1578 
1579     style = gtk_widget_get_style (widget);
1580     font_desc = pango_font_description_copy (style->font_desc);
1581 
1582     /* Use twice the size of the style's font */
1583     if (pango_font_description_get_size_is_absolute (font_desc))
1584       pango_font_description_set_absolute_size (font_desc,
1585                                                 2 * pango_font_description_get_size (font_desc));
1586     else
1587       pango_font_description_set_size (font_desc,
1588                                        2 * pango_font_description_get_size (font_desc));
1589 
1590     gucharmap_chartable_set_font_desc_internal (chartable, font_desc /* adopts */);
1591     g_assert (priv->font_desc != NULL);
1592   }
1593 
1594   /* FIXME: necessary? */
1595   /* gtk_widget_queue_draw (widget); */
1596   gtk_widget_queue_resize (widget);
1597 }
1598 
1599 #ifdef ENABLE_ACCESSIBLE
1600 
1601 static AtkObject *
gucharmap_chartable_get_accessible(GtkWidget * widget)1602 gucharmap_chartable_get_accessible (GtkWidget *widget)
1603 {
1604   static gboolean first_time = TRUE;
1605 
1606   if (first_time)
1607     {
1608       AtkObjectFactory *factory;
1609       AtkRegistry *registry;
1610       GType derived_type;
1611       GType derived_atk_type;
1612 
1613       /*
1614        * Figure out whether accessibility is enabled by looking at the
1615        * type of the accessible object which would be created for
1616        * the parent type of GucharmapChartable.
1617        */
1618       derived_type = g_type_parent (GUCHARMAP_TYPE_CHARTABLE);
1619 
1620       registry = atk_get_default_registry ();
1621       factory = atk_registry_get_factory (registry,
1622                                           derived_type);
1623       derived_atk_type = atk_object_factory_get_accessible_type (factory);
1624       if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
1625 	atk_registry_set_factory_type (registry,
1626 				       GUCHARMAP_TYPE_CHARTABLE,
1627 				       gucharmap_chartable_accessible_factory_get_type ());
1628       first_time = FALSE;
1629     }
1630 
1631   return GTK_WIDGET_CLASS (gucharmap_chartable_parent_class)->get_accessible (widget);
1632 }
1633 
1634 #endif
1635 
1636 /* GucharmapChartable class methods */
1637 
1638 static void
gucharmap_chartable_set_hadjustment(GucharmapChartable * chartable,GtkAdjustment * hadjustment)1639 gucharmap_chartable_set_hadjustment (GucharmapChartable *chartable,
1640                                      GtkAdjustment *hadjustment)
1641 {
1642   GucharmapChartablePrivate *priv = chartable->priv;
1643 
1644   if (hadjustment == priv->hadjustment)
1645     return;
1646 
1647   if (priv->hadjustment)
1648     g_object_unref (priv->hadjustment);
1649 
1650   priv->hadjustment = hadjustment ? g_object_ref_sink (hadjustment) : NULL;
1651 }
1652 
1653 static void
gucharmap_chartable_set_vadjustment(GucharmapChartable * chartable,GtkAdjustment * vadjustment)1654 gucharmap_chartable_set_vadjustment (GucharmapChartable *chartable,
1655                                      GtkAdjustment *vadjustment)
1656 {
1657   GucharmapChartablePrivate *priv = chartable->priv;
1658 
1659   if (vadjustment)
1660     g_return_if_fail (GTK_IS_ADJUSTMENT (vadjustment));
1661   else
1662     vadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1663 
1664   if (priv->vadjustment)
1665     {
1666       g_signal_handler_disconnect (priv->vadjustment,
1667                                    priv->vadjustment_changed_handler_id);
1668       priv->vadjustment_changed_handler_id = 0;
1669       g_object_unref (priv->vadjustment);
1670       priv->vadjustment = NULL;
1671     }
1672 
1673   if (vadjustment)
1674     {
1675       priv->vadjustment = g_object_ref_sink (vadjustment);
1676       priv->vadjustment_changed_handler_id =
1677           g_signal_connect (vadjustment, "value-changed",
1678                             G_CALLBACK (vadjustment_value_changed_cb),
1679                             chartable);
1680     }
1681 
1682   update_scrollbar_adjustment (chartable);
1683 }
1684 
1685 static void
gucharmap_chartable_add_move_binding(GtkBindingSet * binding_set,guint keyval,guint modmask,GtkMovementStep step,gint count)1686 gucharmap_chartable_add_move_binding (GtkBindingSet  *binding_set,
1687                                       guint           keyval,
1688                                       guint           modmask,
1689                                       GtkMovementStep step,
1690                                       gint            count)
1691 {
1692   gtk_binding_entry_add_signal (binding_set, keyval, modmask,
1693                                 "move-cursor", 2,
1694                                 G_TYPE_ENUM, step,
1695                                 G_TYPE_INT, count);
1696 
1697   gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
1698                                 "move-cursor", 2,
1699                                 G_TYPE_ENUM, step,
1700                                 G_TYPE_INT, count);
1701 
1702   if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
1703    return;
1704 
1705   gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
1706                                 "move-cursor", 2,
1707                                 G_TYPE_ENUM, step,
1708                                 G_TYPE_INT, count);
1709 
1710   gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
1711                                 "move-cursor", 2,
1712                                 G_TYPE_ENUM, step,
1713                                 G_TYPE_INT, count);
1714 }
1715 
1716 static void
gucharmap_chartable_move_cursor_left_right(GucharmapChartable * chartable,int count)1717 gucharmap_chartable_move_cursor_left_right (GucharmapChartable *chartable,
1718                                             int count)
1719 {
1720   GucharmapChartablePrivate *priv = chartable->priv;
1721   GtkWidget *widget = GTK_WIDGET (chartable);
1722   gboolean is_rtl;
1723   int offset;
1724 
1725   is_rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1726   offset = is_rtl ? -count : count;
1727   gucharmap_chartable_set_active_cell (chartable, priv->active_cell + offset);
1728 }
1729 
1730 static void
gucharmap_chartable_move_cursor_up_down(GucharmapChartable * chartable,int count)1731 gucharmap_chartable_move_cursor_up_down (GucharmapChartable *chartable,
1732                                          int count)
1733 {
1734   GucharmapChartablePrivate *priv = chartable->priv;
1735 
1736   gucharmap_chartable_set_active_cell (chartable,
1737                                        priv->active_cell + priv->cols * count);
1738 }
1739 
1740 static void
gucharmap_chartable_move_cursor_page_up_down(GucharmapChartable * chartable,int count)1741 gucharmap_chartable_move_cursor_page_up_down (GucharmapChartable *chartable,
1742                                               int count)
1743 {
1744   GucharmapChartablePrivate *priv = chartable->priv;
1745 
1746   gucharmap_chartable_set_active_cell (chartable,
1747                                        priv->active_cell + priv->page_size * count);
1748 }
1749 
1750 static void
gucharmap_chartable_move_cursor_start_end(GucharmapChartable * chartable,int count)1751 gucharmap_chartable_move_cursor_start_end (GucharmapChartable *chartable,
1752                                            int count)
1753 {
1754   GucharmapChartablePrivate *priv = chartable->priv;
1755   int new_cell;
1756 
1757   if (count > 0)
1758     new_cell = priv->last_cell;
1759   else
1760     new_cell = 0;
1761 
1762   gucharmap_chartable_set_active_cell (chartable, new_cell);
1763 }
1764 
1765 static gboolean
gucharmap_chartable_move_cursor(GucharmapChartable * chartable,GtkMovementStep step,int count)1766 gucharmap_chartable_move_cursor (GucharmapChartable *chartable,
1767                                  GtkMovementStep     step,
1768                                  int                 count)
1769 {
1770   g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS ||
1771                         step == GTK_MOVEMENT_VISUAL_POSITIONS ||
1772                         step == GTK_MOVEMENT_DISPLAY_LINES ||
1773                         step == GTK_MOVEMENT_PAGES ||
1774                         step == GTK_MOVEMENT_BUFFER_ENDS, FALSE);
1775 
1776   switch (step)
1777     {
1778     case GTK_MOVEMENT_LOGICAL_POSITIONS:
1779     case GTK_MOVEMENT_VISUAL_POSITIONS:
1780       gucharmap_chartable_move_cursor_left_right (chartable, count);
1781       break;
1782     case GTK_MOVEMENT_DISPLAY_LINES:
1783       gucharmap_chartable_move_cursor_up_down (chartable, count);
1784       break;
1785     case GTK_MOVEMENT_PAGES:
1786       gucharmap_chartable_move_cursor_page_up_down (chartable, count);
1787       break;
1788     case GTK_MOVEMENT_BUFFER_ENDS:
1789       gucharmap_chartable_move_cursor_start_end (chartable, count);
1790       break;
1791     default:
1792       g_assert_not_reached ();
1793     }
1794 
1795   return TRUE;
1796 }
1797 
1798 static void
gucharmap_chartable_copy_clipboard(GucharmapChartable * chartable)1799 gucharmap_chartable_copy_clipboard (GucharmapChartable *chartable)
1800 {
1801   GtkClipboard *clipboard;
1802   gunichar wc;
1803   gchar utf8[7];
1804   gsize len;
1805 
1806   wc = gucharmap_chartable_get_active_character (chartable);
1807   if (!gucharmap_unichar_validate (wc))
1808     return;
1809 
1810   len = g_unichar_to_utf8 (wc, utf8);
1811 
1812   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (chartable),
1813                                         GDK_SELECTION_CLIPBOARD);
1814   gtk_clipboard_set_text (clipboard, utf8, len);
1815 }
1816 
1817 static void
gucharmap_chartable_paste_received_cb(GtkClipboard * clipboard,const char * text,gpointer user_data)1818 gucharmap_chartable_paste_received_cb (GtkClipboard *clipboard,
1819                                        const char *text,
1820                                        gpointer user_data)
1821 {
1822   gpointer *data = (gpointer *) user_data;
1823   GucharmapChartable *chartable = *data;
1824   gunichar wc;
1825 
1826   g_slice_free (gpointer, data);
1827 
1828   if (!chartable)
1829     return;
1830 
1831   g_object_remove_weak_pointer (G_OBJECT (chartable), data);
1832 
1833   if (!text)
1834     return;
1835 
1836   wc = g_utf8_get_char_validated (text, -1);
1837   if (wc == 0 ||
1838       !gucharmap_unichar_validate (wc)) {
1839     gtk_widget_error_bell (GTK_WIDGET (chartable));
1840     return;
1841   }
1842 
1843   gucharmap_chartable_set_active_character (chartable, wc);
1844 }
1845 
1846 static void
gucharmap_chartable_paste_clipboard(GucharmapChartable * chartable)1847 gucharmap_chartable_paste_clipboard (GucharmapChartable *chartable)
1848 {
1849   GtkClipboard *clipboard;
1850   gpointer *data;
1851 
1852   if (!gtk_widget_get_realized (GTK_WIDGET (chartable)))
1853     return;
1854 
1855   data = g_slice_new (gpointer);
1856   *data = chartable;
1857   g_object_add_weak_pointer (G_OBJECT (chartable), data);
1858 
1859   clipboard = gtk_widget_get_clipboard (GTK_WIDGET (chartable),
1860                                         GDK_SELECTION_CLIPBOARD);
1861   gtk_clipboard_request_text (clipboard,
1862                               gucharmap_chartable_paste_received_cb,
1863                               data);
1864 }
1865 
1866 /* does all the initial construction */
1867 static void
gucharmap_chartable_init(GucharmapChartable * chartable)1868 gucharmap_chartable_init (GucharmapChartable *chartable)
1869 {
1870   GtkWidget *widget = GTK_WIDGET (chartable);
1871   GucharmapChartablePrivate *priv;
1872 
1873   priv = chartable->priv = G_TYPE_INSTANCE_GET_PRIVATE (chartable, GUCHARMAP_TYPE_CHARTABLE, GucharmapChartablePrivate);
1874 
1875   priv->page_first_cell = 0;
1876   priv->active_cell = 0;
1877   priv->rows = 1;
1878   priv->cols = 1;
1879   priv->zoom_mode_enabled = TRUE;
1880   priv->zoom_window = NULL;
1881   priv->snap_pow2_enabled = FALSE;
1882   priv->font_fallback = TRUE;
1883 
1884   priv->vadjustment = NULL;
1885   priv->hadjustment = NULL;
1886   priv->hscroll_policy = GTK_SCROLL_NATURAL;
1887   priv->vscroll_policy = GTK_SCROLL_NATURAL;
1888 
1889 /* This didn't fix the slow expose events either: */
1890 /*  gtk_widget_set_double_buffered (widget, FALSE); */
1891 
1892   gtk_widget_set_events (widget,
1893           GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK |
1894           GDK_BUTTON_RELEASE_MASK | GDK_BUTTON3_MOTION_MASK |
1895           GDK_BUTTON1_MOTION_MASK | GDK_FOCUS_CHANGE_MASK |
1896           GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
1897 
1898   priv->target_list = gtk_target_list_new (NULL, 0);
1899   gtk_target_list_add_text_targets (priv->target_list, 0);
1900 
1901   gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_ALL,
1902                      NULL, 0,
1903                      GDK_ACTION_COPY);
1904   gtk_drag_dest_add_text_targets (widget);
1905 
1906   /* this is required to get key_press events */
1907   gtk_widget_set_can_focus (widget, TRUE);
1908 
1909   gucharmap_chartable_set_codepoint_list (chartable, NULL);
1910 
1911   gtk_widget_show_all (GTK_WIDGET (chartable));
1912 }
1913 
1914 static void
gucharmap_chartable_finalize(GObject * object)1915 gucharmap_chartable_finalize (GObject *object)
1916 {
1917   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (object);
1918   GucharmapChartablePrivate *priv = chartable->priv;
1919 
1920   if (priv->font_desc)
1921     pango_font_description_free (priv->font_desc);
1922 
1923   gucharmap_chartable_clear_pango_layout (chartable);
1924 
1925   gtk_target_list_unref (priv->target_list);
1926 
1927   if (priv->codepoint_list)
1928     g_object_unref (priv->codepoint_list);
1929 
1930   destroy_zoom_window (chartable);
1931 
1932   G_OBJECT_CLASS (gucharmap_chartable_parent_class)->finalize (object);
1933 }
1934 
1935 static void
gucharmap_chartable_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1936 gucharmap_chartable_set_property (GObject *object,
1937                                   guint prop_id,
1938                                   const GValue *value,
1939                                   GParamSpec *pspec)
1940 {
1941   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (object);
1942   GucharmapChartablePrivate *priv = chartable->priv;
1943 
1944   switch (prop_id) {
1945     case PROP_HADJUSTMENT:
1946       gucharmap_chartable_set_hadjustment (chartable, g_value_get_object (value));
1947       break;
1948     case PROP_VADJUSTMENT:
1949       gucharmap_chartable_set_vadjustment (chartable, g_value_get_object (value));
1950       break;
1951     case PROP_HSCROLL_POLICY:
1952       priv->hscroll_policy = g_value_get_enum (value);
1953       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (chartable));
1954       break;
1955     case PROP_VSCROLL_POLICY:
1956       priv->vscroll_policy = g_value_get_enum (value);
1957       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (chartable));
1958       break;
1959     case PROP_ACTIVE_CHAR:
1960       gucharmap_chartable_set_active_character (chartable, g_value_get_uint (value));
1961       break;
1962     case PROP_CODEPOINT_LIST:
1963       gucharmap_chartable_set_codepoint_list (chartable, g_value_get_object (value));
1964       break;
1965     case PROP_FONT_DESC:
1966       gucharmap_chartable_set_font_desc (chartable, g_value_get_boxed (value));
1967       break;
1968     case PROP_FONT_FALLBACK:
1969       gucharmap_chartable_set_font_fallback (chartable, g_value_get_boolean (value));
1970       break;
1971     case PROP_SNAP_POW2:
1972       gucharmap_chartable_set_snap_pow2 (chartable, g_value_get_boolean (value));
1973       break;
1974     case PROP_ZOOM_ENABLED:
1975       gucharmap_chartable_set_zoom_enabled (chartable, g_value_get_boolean (value));
1976       break;
1977     case PROP_ZOOM_SHOWING:
1978       /* not writable */
1979       break;
1980     default:
1981       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1982       break;
1983   }
1984 }
1985 
1986 static void
gucharmap_chartable_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1987 gucharmap_chartable_get_property (GObject *object,
1988                                   guint prop_id,
1989                                   GValue *value,
1990                                   GParamSpec *pspec)
1991 {
1992   GucharmapChartable *chartable = GUCHARMAP_CHARTABLE (object);
1993   GucharmapChartablePrivate *priv = chartable->priv;
1994 
1995   switch (prop_id) {
1996     case PROP_HADJUSTMENT:
1997       g_value_set_object (value, NULL);
1998       break;
1999     case PROP_VADJUSTMENT:
2000       g_value_set_object (value, priv->vadjustment);
2001       break;
2002     case PROP_HSCROLL_POLICY:
2003       g_value_set_enum (value, priv->hscroll_policy);
2004       break;
2005     case PROP_VSCROLL_POLICY:
2006       g_value_set_enum (value, priv->vscroll_policy);
2007       break;
2008     case PROP_ACTIVE_CHAR:
2009       g_value_set_uint (value, gucharmap_chartable_get_active_character (chartable));
2010       break;
2011     case PROP_CODEPOINT_LIST:
2012       g_value_set_object (value, gucharmap_chartable_get_codepoint_list (chartable));
2013       break;
2014     case PROP_FONT_DESC:
2015       g_value_set_boxed (value, gucharmap_chartable_get_font_desc (chartable));
2016       break;
2017     case PROP_FONT_FALLBACK:
2018       g_value_set_boolean (value, gucharmap_chartable_get_font_fallback (chartable));
2019       break;
2020     case PROP_SNAP_POW2:
2021       g_value_set_boolean (value, priv->snap_pow2_enabled);
2022       break;
2023     case PROP_ZOOM_ENABLED:
2024       g_value_set_boolean (value, priv->zoom_mode_enabled);
2025       break;
2026     case PROP_ZOOM_SHOWING:
2027       g_value_set_boolean (value, priv->zoom_window != NULL);
2028       break;
2029     default:
2030       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2031       break;
2032   }
2033 }
2034 
2035 static void
gucharmap_chartable_class_init(GucharmapChartableClass * klass)2036 gucharmap_chartable_class_init (GucharmapChartableClass *klass)
2037 {
2038   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2039   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2040   GtkBindingSet *binding_set;
2041 
2042   g_type_class_add_private (object_class, sizeof (GucharmapChartablePrivate));
2043 
2044   object_class->finalize = gucharmap_chartable_finalize;
2045   object_class->get_property = gucharmap_chartable_get_property;
2046   object_class->set_property = gucharmap_chartable_set_property;
2047 
2048   widget_class->drag_begin = gucharmap_chartable_drag_begin;
2049   widget_class->drag_data_get = gucharmap_chartable_drag_data_get;
2050   widget_class->drag_data_received = gucharmap_chartable_drag_data_received;
2051   widget_class->button_press_event = gucharmap_chartable_button_press;
2052   widget_class->button_release_event = gucharmap_chartable_button_release;
2053   widget_class->get_preferred_width = gucharmap_chartable_get_preferred_width;
2054   widget_class->get_preferred_height = gucharmap_chartable_get_preferred_height;
2055   widget_class->draw = gucharmap_chartable_draw;
2056   widget_class->focus_in_event = gucharmap_chartable_focus_in_event;
2057   widget_class->focus_out_event = gucharmap_chartable_focus_out_event;
2058   widget_class->key_press_event = gucharmap_chartable_key_press_event;
2059   widget_class->key_release_event = gucharmap_chartable_key_release_event;
2060   widget_class->motion_notify_event = gucharmap_chartable_motion_notify;
2061   widget_class->size_allocate = gucharmap_chartable_size_allocate;
2062   widget_class->style_set = gucharmap_chartable_style_set;
2063 #ifdef ENABLE_ACCESSIBLE
2064   widget_class->get_accessible = gucharmap_chartable_get_accessible;
2065 #endif
2066 
2067   klass->move_cursor = gucharmap_chartable_move_cursor;
2068   klass->activate = NULL;
2069   klass->copy_clipboard = gucharmap_chartable_copy_clipboard;
2070   klass->paste_clipboard = gucharmap_chartable_paste_clipboard;
2071   klass->set_active_char = NULL;
2072 
2073   widget_class->activate_signal = signals[ACTIVATE] =
2074     g_signal_new (I_("activate"),
2075                   G_TYPE_FROM_CLASS (object_class),
2076                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
2077                   G_STRUCT_OFFSET (GucharmapChartableClass, activate),
2078                   NULL, NULL,
2079                   g_cclosure_marshal_VOID__VOID,
2080                   G_TYPE_NONE,
2081                   0);
2082 
2083   /* GtkScrollable interface properties */
2084   g_object_class_override_property (object_class, PROP_HADJUSTMENT, "hadjustment");
2085   g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment");
2086   g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
2087   g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
2088 
2089   signals[STATUS_MESSAGE] =
2090     g_signal_new (I_("status-message"), gucharmap_chartable_get_type (), G_SIGNAL_RUN_FIRST,
2091                   G_STRUCT_OFFSET (GucharmapChartableClass, status_message),
2092                   NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE,
2093                   1, G_TYPE_STRING);
2094 
2095   signals[MOVE_CURSOR] =
2096     g_signal_new (I_("move-cursor"),
2097                   G_TYPE_FROM_CLASS (object_class),
2098                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
2099                   G_STRUCT_OFFSET (GucharmapChartableClass, move_cursor),
2100                   NULL, NULL,
2101                   _gucharmap_marshal_BOOLEAN__ENUM_INT,
2102                   G_TYPE_BOOLEAN, 2,
2103                   GTK_TYPE_MOVEMENT_STEP,
2104                   G_TYPE_INT);
2105 
2106   signals[COPY_CLIPBOARD] =
2107     g_signal_new (I_("copy-clipboard"),
2108                   G_OBJECT_CLASS_TYPE (object_class),
2109                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
2110                   G_STRUCT_OFFSET (GucharmapChartableClass, copy_clipboard),
2111                   NULL, NULL,
2112                   g_cclosure_marshal_VOID__VOID,
2113                   G_TYPE_NONE, 0);
2114 
2115   signals[PASTE_CLIPBOARD] =
2116     g_signal_new (I_("paste-clipboard"),
2117                   G_OBJECT_CLASS_TYPE (object_class),
2118                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
2119                   G_STRUCT_OFFSET (GucharmapChartableClass, paste_clipboard),
2120                   NULL, NULL,
2121                   g_cclosure_marshal_VOID__VOID,
2122                   G_TYPE_NONE, 0);
2123 
2124   /* Not using g_param_spec_unichar on purpose, since it disallows certain values
2125    * we want (it's performing a g_unichar_validate).
2126    */
2127   g_object_class_install_property
2128     (object_class,
2129      PROP_ACTIVE_CHAR,
2130      g_param_spec_uint ("active-character", NULL, NULL,
2131                         0,
2132                         UNICHAR_MAX,
2133                         0,
2134                         G_PARAM_READWRITE |
2135                         G_PARAM_STATIC_NAME |
2136                         G_PARAM_STATIC_NICK |
2137                         G_PARAM_STATIC_BLURB));
2138 
2139   g_object_class_install_property
2140     (object_class,
2141      PROP_CODEPOINT_LIST,
2142      g_param_spec_object ("codepoint-list", NULL, NULL,
2143                           gucharmap_codepoint_list_get_type (),
2144                           G_PARAM_READWRITE |
2145                           G_PARAM_STATIC_NAME |
2146                           G_PARAM_STATIC_NICK |
2147                           G_PARAM_STATIC_BLURB));
2148 
2149   g_object_class_install_property
2150     (object_class,
2151      PROP_FONT_DESC,
2152      g_param_spec_boxed ("font-desc", NULL, NULL,
2153                          PANGO_TYPE_FONT_DESCRIPTION,
2154                          G_PARAM_READWRITE |
2155                          G_PARAM_STATIC_NAME |
2156                          G_PARAM_STATIC_NICK |
2157                          G_PARAM_STATIC_BLURB));
2158 
2159   /**
2160    * GucharmapChartable:font-fallback:
2161    *
2162    * Whether font fallback is enabled.
2163    *
2164    * Since: 2.34
2165    */
2166   g_object_class_install_property
2167     (object_class,
2168      PROP_FONT_FALLBACK,
2169      g_param_spec_boolean ("font-fallback", NULL, NULL,
2170                            TRUE,
2171                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2172 
2173   g_object_class_install_property
2174     (object_class,
2175      PROP_SNAP_POW2,
2176      g_param_spec_boolean ("snap-power-2", NULL, NULL,
2177                            FALSE,
2178                            G_PARAM_READWRITE |
2179                            G_PARAM_STATIC_NAME |
2180                            G_PARAM_STATIC_NICK |
2181                            G_PARAM_STATIC_BLURB));
2182 
2183   g_object_class_install_property
2184     (object_class,
2185      PROP_ZOOM_ENABLED,
2186      g_param_spec_boolean ("zoom-enabled", NULL, NULL,
2187                            FALSE,
2188                            G_PARAM_READWRITE |
2189                            G_PARAM_STATIC_NAME |
2190                            G_PARAM_STATIC_NICK |
2191                            G_PARAM_STATIC_BLURB));
2192 
2193   g_object_class_install_property
2194     (object_class,
2195      PROP_ZOOM_SHOWING,
2196      g_param_spec_boolean ("zoom-showing", NULL, NULL,
2197                            FALSE,
2198                            G_PARAM_READABLE |
2199                            G_PARAM_STATIC_NAME |
2200                            G_PARAM_STATIC_NICK |
2201                            G_PARAM_STATIC_BLURB));
2202 
2203   /* Keybindings */
2204   binding_set = gtk_binding_set_by_class (klass);
2205 
2206   /* Cursor movement */
2207   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Up, 0,
2208                                         GTK_MOVEMENT_DISPLAY_LINES, -1);
2209   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Up, 0,
2210                                         GTK_MOVEMENT_DISPLAY_LINES, -1);
2211 
2212   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Down, 0,
2213                                         GTK_MOVEMENT_DISPLAY_LINES, 1);
2214   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Down, 0,
2215                                         GTK_MOVEMENT_DISPLAY_LINES, 1);
2216 
2217   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_p, GDK_CONTROL_MASK,
2218                                         GTK_MOVEMENT_DISPLAY_LINES, -1);
2219 
2220   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_n, GDK_CONTROL_MASK,
2221                                         GTK_MOVEMENT_DISPLAY_LINES, 1);
2222 
2223   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Home, 0,
2224                                         GTK_MOVEMENT_BUFFER_ENDS, -1);
2225   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
2226                                         GTK_MOVEMENT_BUFFER_ENDS, -1);
2227 
2228   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_End, 0,
2229                                         GTK_MOVEMENT_BUFFER_ENDS, 1);
2230   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_End, 0,
2231                                         GTK_MOVEMENT_BUFFER_ENDS, 1);
2232 
2233   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Page_Up, 0,
2234                                         GTK_MOVEMENT_PAGES, -1);
2235   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
2236                                         GTK_MOVEMENT_PAGES, -1);
2237 
2238   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Page_Down, 0,
2239                                         GTK_MOVEMENT_PAGES, 1);
2240   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
2241                                         GTK_MOVEMENT_PAGES, 1);
2242 
2243   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Left, 0,
2244                                         GTK_MOVEMENT_VISUAL_POSITIONS, -1);
2245   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Left, 0,
2246                                         GTK_MOVEMENT_VISUAL_POSITIONS, -1);
2247 
2248   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_Right, 0,
2249                                         GTK_MOVEMENT_VISUAL_POSITIONS, 1);
2250   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_KP_Right, 0,
2251                                         GTK_MOVEMENT_VISUAL_POSITIONS, 1);
2252 
2253   /* Activate */
2254   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
2255                                 "activate", 0);
2256   gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
2257                                 "activate", 0);
2258   gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
2259                                 "activate", 0);
2260   gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
2261                                 "activate", 0);
2262 
2263   /* Clipboard actions */
2264   gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK,
2265                                 "copy-clipboard", 0);
2266   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_CONTROL_MASK,
2267                                 "copy-clipboard", 0);
2268   gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK,
2269                                 "paste-clipboard", 0);
2270   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_SHIFT_MASK,
2271                                 "paste-clipboard", 0);
2272 
2273 #if 0
2274   /* VI keybindings */
2275   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_k, 0,
2276                                         GTK_MOVEMENT_DISPLAY_LINES, -1);
2277   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_K, 0,
2278                                         GTK_MOVEMENT_DISPLAY_LINES, -1);
2279 
2280   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_j, 0,
2281                                         GTK_MOVEMENT_DISPLAY_LINES, 1);
2282   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_J, 0,
2283                                         GTK_MOVEMENT_DISPLAY_LINES, 1);
2284 
2285   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_b, 0,
2286                                         GTK_MOVEMENT_PAGES, -1);
2287   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_B, 0,
2288                                         GTK_MOVEMENT_PAGES, -1);
2289 
2290   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_h, 0,
2291                                         GTK_MOVEMENT_VISUAL_POSITIONS, -1);
2292   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_H, 0,
2293                                         GTK_MOVEMENT_VISUAL_POSITIONS, -1);
2294 
2295   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_l, 0,
2296                                         GTK_MOVEMENT_VISUAL_POSITIONS, 1);
2297   gucharmap_chartable_add_move_binding (binding_set, GDK_KEY_L, 0,
2298                                         GTK_MOVEMENT_VISUAL_POSITIONS, 1);
2299 #endif
2300 }
2301 
2302 /* public API */
2303 
2304 /**
2305  * gucharmap_chartable_new:
2306  *
2307  * Returns: a new #GucharmapChartable
2308  */
2309 GtkWidget *
gucharmap_chartable_new(void)2310 gucharmap_chartable_new (void)
2311 {
2312   return GTK_WIDGET (g_object_new (gucharmap_chartable_get_type (), NULL));
2313 }
2314 
2315 /**
2316  * gucharmap_chartable_set_zoom_enabled:
2317  * @chartable: a #GucharmapChartable
2318  * @enabled: whether to enable zoom mode
2319  *
2320  * Enables or disables the zoom popup.
2321  */
2322 void
gucharmap_chartable_set_zoom_enabled(GucharmapChartable * chartable,gboolean enabled)2323 gucharmap_chartable_set_zoom_enabled (GucharmapChartable *chartable,
2324                                       gboolean enabled)
2325 {
2326   GucharmapChartablePrivate *priv;
2327   GObject *object;
2328 
2329   g_return_if_fail (GUCHARMAP_IS_CHARTABLE (chartable));
2330 
2331   priv = chartable->priv;
2332 
2333   enabled = enabled != FALSE;
2334   if (priv->zoom_mode_enabled == enabled)
2335     return;
2336 
2337   object = G_OBJECT (chartable);
2338   g_object_freeze_notify (object);
2339 
2340   priv->zoom_mode_enabled = enabled;
2341   if (!enabled)
2342     gucharmap_chartable_hide_zoom (chartable);
2343 
2344   g_object_notify (object, "zoom-enabled");
2345   g_object_thaw_notify (object);
2346 }
2347 
2348 /**
2349  * gucharmap_chartable_get_zoom_enabled:
2350  * @chartable: a #GucharmapChartable
2351  *
2352  * Returns: whether zooming is enabled
2353  */
2354 gboolean
gucharmap_chartable_get_zoom_enabled(GucharmapChartable * chartable)2355 gucharmap_chartable_get_zoom_enabled (GucharmapChartable *chartable)
2356 {
2357   g_return_val_if_fail (GUCHARMAP_IS_CHARTABLE (chartable), FALSE);
2358 
2359   return chartable->priv->zoom_mode_enabled;
2360 }
2361 
2362 /**
2363  * gucharmap_chartable_set_font_desc:
2364  * @chartable: a #GucharmapChartable
2365  * @font_desc: a #PangoFontDescription
2366  *
2367  * Sets @font_desc as the font to use to display the character table.
2368  */
2369 void
gucharmap_chartable_set_font_desc(GucharmapChartable * chartable,PangoFontDescription * font_desc)2370 gucharmap_chartable_set_font_desc (GucharmapChartable *chartable,
2371                                    PangoFontDescription *font_desc)
2372 {
2373   GucharmapChartablePrivate *priv;
2374 
2375   g_return_if_fail (GUCHARMAP_IS_CHARTABLE (chartable));
2376   g_return_if_fail (font_desc != NULL);
2377 
2378   priv = chartable->priv;
2379 
2380   if (priv->font_desc &&
2381       pango_font_description_equal (font_desc, priv->font_desc))
2382     return;
2383 
2384   gucharmap_chartable_set_font_desc_internal (chartable,
2385                                               pango_font_description_copy (font_desc));
2386 }
2387 
2388 /**
2389  * gucharmap_chartable_get_font_desc:
2390  * @chartable: a #GucharmapChartable
2391  *
2392  * Returns: the #PangoFontDescription used to display the character table.
2393  *   The returned object is owned by @chartable and must not be modified or freed.
2394  */
2395 PangoFontDescription *
gucharmap_chartable_get_font_desc(GucharmapChartable * chartable)2396 gucharmap_chartable_get_font_desc (GucharmapChartable *chartable)
2397 {
2398   g_return_val_if_fail (GUCHARMAP_IS_CHARTABLE (chartable), NULL);
2399 
2400   return chartable->priv->font_desc;
2401 }
2402 
2403 /**
2404  * gucharmap_chartable_set_font_fallback:
2405  * @chartable: a #GucharmapChartable
2406  * @enable_font_fallback: whether to enable font fallback
2407  *
2408  * Since: 2.34
2409  */
2410 void
gucharmap_chartable_set_font_fallback(GucharmapChartable * chartable,gboolean enable_font_fallback)2411 gucharmap_chartable_set_font_fallback (GucharmapChartable *chartable,
2412                                        gboolean enable_font_fallback)
2413 {
2414   GucharmapChartablePrivate *priv;
2415   GtkWidget *widget;
2416 
2417   g_return_if_fail (GUCHARMAP_IS_CHARTABLE (chartable));
2418 
2419   priv = chartable->priv;
2420   enable_font_fallback = enable_font_fallback != FALSE;
2421   if (enable_font_fallback == priv->font_fallback)
2422     return;
2423 
2424   priv->font_fallback = enable_font_fallback;
2425   g_object_notify (G_OBJECT (chartable), "font-fallback");
2426 
2427   gucharmap_chartable_clear_pango_layout (chartable);
2428 
2429   widget = GTK_WIDGET (chartable);
2430   if (gtk_widget_get_realized (widget))
2431     gtk_widget_queue_draw (widget);
2432 }
2433 
2434 /**
2435  * gucharmap_chartable_get_font_fallback:
2436  * @chartable: a #GucharmapChartable
2437  *
2438  * Returns: whether font fallback is enabled
2439  *
2440  * Since: 2.34
2441  */
2442 gboolean
gucharmap_chartable_get_font_fallback(GucharmapChartable * chartable)2443 gucharmap_chartable_get_font_fallback (GucharmapChartable *chartable)
2444 {
2445   g_return_val_if_fail (GUCHARMAP_IS_CHARTABLE (chartable), FALSE);
2446 
2447   return chartable->priv->font_fallback;
2448 }
2449 
2450 /**
2451  * gucharmap_chartable_get_active_character:
2452  * @chartable: a #GucharmapChartable
2453  *
2454  * Returns: the currently selected character
2455  */
2456 gunichar
gucharmap_chartable_get_active_character(GucharmapChartable * chartable)2457 gucharmap_chartable_get_active_character (GucharmapChartable *chartable)
2458 {
2459   GucharmapChartablePrivate *priv = chartable->priv;
2460 
2461   if (!priv->codepoint_list)
2462     return 0;
2463 
2464   return gucharmap_codepoint_list_get_char (priv->codepoint_list, priv->active_cell);
2465 }
2466 
2467 /**
2468  * gucharmap_chartable_set_active_character:
2469  * @chartable: a #GucharmapChartable
2470  * @wc: a unicode character (UTF-32)
2471  *
2472  * Sets @wc as the selected character.
2473  */
2474 void
gucharmap_chartable_set_active_character(GucharmapChartable * chartable,gunichar wc)2475 gucharmap_chartable_set_active_character (GucharmapChartable *chartable,
2476                                           gunichar wc)
2477 {
2478   set_active_char (chartable, wc);
2479 }
2480 
2481 /**
2482  * gucharmap_chartable_set_snap_pow2:
2483  * @chartable: a #GucharmapChartable
2484  * @snap: whether to enable or disable snapping
2485  *
2486  * Sets whether the number columns the character table shows should
2487  * always be a power of 2.
2488  */
2489 void
gucharmap_chartable_set_snap_pow2(GucharmapChartable * chartable,gboolean snap)2490 gucharmap_chartable_set_snap_pow2 (GucharmapChartable *chartable,
2491                                    gboolean snap)
2492 {
2493   GucharmapChartablePrivate *priv = chartable->priv;
2494 
2495   snap = snap != FALSE;
2496 
2497   if (snap != priv->snap_pow2_enabled)
2498     {
2499       priv->snap_pow2_enabled = snap;
2500 
2501       gtk_widget_queue_resize (GTK_WIDGET (chartable));
2502 
2503       g_object_notify (G_OBJECT (chartable), "snap-power-2");
2504     }
2505 }
2506 
2507 /**
2508  * gucharmap_chartable_get_snap_pow2:
2509  * @chartable: a #GucharmapChartable
2510  *
2511  * Returns whether the number of columns the character table shows is
2512  * always a power of 2.
2513  */
2514 gboolean
gucharmap_chartable_get_snap_pow2(GucharmapChartable * chartable)2515 gucharmap_chartable_get_snap_pow2 (GucharmapChartable *chartable)
2516 {
2517   GucharmapChartablePrivate *priv = chartable->priv;
2518 
2519   return priv->snap_pow2_enabled;
2520 }
2521 
2522 /**
2523  * gucharmap_chartable_set_codepoint_list:
2524  * @chartable: a #GucharmapChartable
2525  * @codepoint_list: a #GucharmapCodepointList
2526  *
2527  * Sets the codepoint list to show in the character table.
2528  */
2529 void
gucharmap_chartable_set_codepoint_list(GucharmapChartable * chartable,GucharmapCodepointList * codepoint_list)2530 gucharmap_chartable_set_codepoint_list (GucharmapChartable     *chartable,
2531                                         GucharmapCodepointList *codepoint_list)
2532 {
2533   GucharmapChartablePrivate *priv = chartable->priv;
2534   GObject *object = G_OBJECT (chartable);
2535   GtkWidget *widget = GTK_WIDGET (chartable);
2536 
2537   g_object_freeze_notify (object);
2538 
2539   if (codepoint_list)
2540     g_object_ref (codepoint_list);
2541   if (priv->codepoint_list)
2542     g_object_unref (priv->codepoint_list);
2543   priv->codepoint_list = codepoint_list;
2544   priv->codepoint_list_changed = TRUE;
2545 
2546   priv->active_cell = 0;
2547   priv->page_first_cell = 0;
2548   if (codepoint_list)
2549     priv->last_cell = gucharmap_codepoint_list_get_last_index (codepoint_list);
2550   else
2551     priv->last_cell = 0;
2552 
2553   g_object_notify (object, "codepoint-list");
2554   g_object_notify (object, "active-character");
2555 
2556   update_scrollbar_adjustment (chartable);
2557 
2558   gtk_widget_queue_draw (widget);
2559 
2560   g_object_thaw_notify (object);
2561 }
2562 
2563 /**
2564  * gucharmap_chartable_get_codepoint_list:
2565  * @chartable: a #GucharmapChartable
2566  *
2567  * Returns: (transfer none): the current codepoint list
2568  */
2569 GucharmapCodepointList *
gucharmap_chartable_get_codepoint_list(GucharmapChartable * chartable)2570 gucharmap_chartable_get_codepoint_list (GucharmapChartable *chartable)
2571 {
2572   GucharmapChartablePrivate *priv = chartable->priv;
2573 
2574   return priv->codepoint_list;
2575 }
2576