]> Pileus Git - ~andy/gtk/blob - modules/other/gail/gailtextcell.c
Use G_DEFINE_TYPE[_WITH_CODE] instead of hand-coding the get_type functions. Bug...
[~andy/gtk] / modules / other / gail / gailtextcell.c
1 /* GAIL - The GNOME Accessibility Enabling Library
2  * Copyright 2001 Sun Microsystems Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <gtk/gtk.h>
21 #include "gailtextcell.h"
22 #include "gailcontainercell.h"
23 #include "gailcellparent.h"
24 #include <libgail-util/gailmisc.h>
25 #include "gail-private-macros.h"
26
27 static void      gail_text_cell_class_init              (GailTextCellClass *klass);
28 static void      gail_text_cell_init                    (GailTextCell   *text_cell);
29 static void      gail_text_cell_finalize                (GObject        *object);
30
31 static G_CONST_RETURN gchar* gail_text_cell_get_name    (AtkObject      *atk_obj);
32
33 static void      atk_text_interface_init                (AtkTextIface   *iface);
34
35 /* atktext.h */
36
37 static gchar*    gail_text_cell_get_text                (AtkText        *text,
38                                                         gint            start_pos,
39                                                         gint            end_pos);
40 static gunichar gail_text_cell_get_character_at_offset  (AtkText        *text,
41                                                          gint           offset);
42 static gchar*   gail_text_cell_get_text_before_offset   (AtkText        *text,
43                                                          gint           offset,
44                                                          AtkTextBoundary boundary_type,
45                                                          gint           *start_offset,
46                                                          gint           *end_offset);
47 static gchar*   gail_text_cell_get_text_at_offset       (AtkText        *text,
48                                                          gint           offset,
49                                                          AtkTextBoundary boundary_type,
50                                                          gint           *start_offset,
51                                                          gint           *end_offset);
52 static gchar*   gail_text_cell_get_text_after_offset    (AtkText        *text,
53                                                          gint           offset,
54                                                          AtkTextBoundary boundary_type,
55                                                          gint           *start_offset,
56                                                          gint           *end_offset);
57 static gint      gail_text_cell_get_character_count     (AtkText        *text);
58 static gint      gail_text_cell_get_caret_offset        (AtkText        *text);
59 static gboolean  gail_text_cell_set_caret_offset        (AtkText        *text,
60                                                          gint           offset);
61 static void      gail_text_cell_get_character_extents   (AtkText        *text,
62                                                          gint           offset,
63                                                          gint           *x,
64                                                          gint           *y,
65                                                          gint           *width,
66                                                          gint           *height,
67                                                          AtkCoordType   coords);
68 static gint      gail_text_cell_get_offset_at_point     (AtkText        *text,
69                                                          gint           x,
70                                                          gint           y,
71                                                          AtkCoordType   coords);
72 static AtkAttributeSet* gail_text_cell_get_run_attributes 
73                                                         (AtkText        *text,
74                                                          gint           offset,
75                                                          gint           *start_offset,      
76                                                          gint           *end_offset); 
77 static AtkAttributeSet* gail_text_cell_get_default_attributes 
78                                                         (AtkText        *text);
79
80 static PangoLayout*     create_pango_layout             (GtkCellRendererText *gtk_renderer,
81                                                          GtkWidget           *widget);
82 static void             add_attr                        (PangoAttrList  *attr_list,
83                                                          PangoAttribute *attr);
84
85 /* Misc */
86
87 static gboolean gail_text_cell_update_cache             (GailRendererCell *cell,
88                                                          gboolean       emit_change_signal);
89
90 gchar *gail_text_cell_property_list[] = {
91   /* Set font_desc first since it resets other values if it is NULL */
92   "font_desc",
93
94   "attributes",
95   "background_gdk",
96   "editable",
97   "family",
98   "foreground_gdk",
99   "rise",
100   "scale",
101   "size",
102   "size_points",
103   "stretch",
104   "strikethrough",
105   "style",
106   "text",
107   "underline",
108   "variant",
109   "weight",
110
111   /* Also need the sets */
112   "background_set",
113   "editable_set",
114   "family_set",
115   "foreground_set",
116   "rise_set",
117   "scale_set",
118   "size_set",
119   "stretch_set",
120   "strikethrough_set",
121   "style_set",
122   "underline_set",
123   "variant_set",
124   "weight_set",
125   NULL
126 };
127
128 G_DEFINE_TYPE_WITH_CODE (GailTextCell, gail_text_cell, GAIL_TYPE_RENDERER_CELL,
129                          G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
130
131 static void 
132 gail_text_cell_class_init (GailTextCellClass *klass)
133 {
134   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
135   AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (klass);
136   GailRendererCellClass *renderer_cell_class = GAIL_RENDERER_CELL_CLASS (klass);
137
138   renderer_cell_class->update_cache = gail_text_cell_update_cache;
139   renderer_cell_class->property_list = gail_text_cell_property_list;
140
141   atk_object_class->get_name = gail_text_cell_get_name;
142
143   gobject_class->finalize = gail_text_cell_finalize;
144 }
145
146 /* atktext.h */
147
148 static void
149 gail_text_cell_init (GailTextCell *text_cell)
150 {
151   text_cell->cell_text = NULL;
152   text_cell->caret_pos = 0;
153   text_cell->cell_length = 0;
154   text_cell->textutil = gail_text_util_new ();
155   atk_state_set_add_state (GAIL_CELL (text_cell)->state_set,
156                            ATK_STATE_SINGLE_LINE);
157 }
158
159 AtkObject* 
160 gail_text_cell_new (void)
161 {
162   GObject *object;
163   AtkObject *atk_object;
164   GailRendererCell *cell;
165
166   object = g_object_new (GAIL_TYPE_TEXT_CELL, NULL);
167
168   g_return_val_if_fail (object != NULL, NULL);
169
170   atk_object = ATK_OBJECT (object);
171   atk_object->role = ATK_ROLE_TABLE_CELL;
172
173   cell = GAIL_RENDERER_CELL(object);
174
175   cell->renderer = gtk_cell_renderer_text_new ();
176   g_object_ref (cell->renderer);
177   gtk_object_sink (GTK_OBJECT (cell->renderer));
178   return atk_object;
179 }
180
181 static void
182 gail_text_cell_finalize (GObject            *object)
183 {
184   GailTextCell *text_cell = GAIL_TEXT_CELL (object);
185
186   g_object_unref (text_cell->textutil);
187   g_free (text_cell->cell_text);
188
189   G_OBJECT_CLASS (gail_text_cell_parent_class)->finalize (object);
190 }
191
192 static G_CONST_RETURN gchar*
193 gail_text_cell_get_name (AtkObject *atk_obj)
194 {
195   if (atk_obj->name)
196     return atk_obj->name;
197   else
198     {
199       GailTextCell *text_cell = GAIL_TEXT_CELL (atk_obj);
200
201       return text_cell->cell_text;
202     }
203 }
204
205 static gboolean
206 gail_text_cell_update_cache (GailRendererCell *cell,
207                              gboolean         emit_change_signal)
208 {
209   GailTextCell *text_cell = GAIL_TEXT_CELL (cell);
210   AtkObject *obj = ATK_OBJECT (cell);
211   gboolean rv = FALSE;
212   gint temp_length;
213   gchar *new_cache;
214
215   g_object_get (G_OBJECT (cell->renderer), "text", &new_cache, NULL);
216
217   if (text_cell->cell_text)
218     {
219      /*
220       * If the new value is NULL and the old value isn't NULL, then the
221       * value has changed.
222       */
223       if (new_cache == NULL ||
224           g_strcasecmp (text_cell->cell_text, new_cache))
225         {
226           g_free (text_cell->cell_text);
227           temp_length = text_cell->cell_length;
228           text_cell->cell_text = NULL;
229           text_cell->cell_length = 0;
230           if (emit_change_signal)
231             {
232               g_signal_emit_by_name (cell, "text_changed::delete", 0, temp_length);
233               if (obj->name == NULL)
234                 g_object_notify (G_OBJECT (obj), "accessible-name");
235             }
236           if (new_cache)
237             rv = TRUE;
238         }
239     }
240   else
241     rv = TRUE;
242
243   if (rv)
244     {
245       if (new_cache == NULL)
246         {
247           text_cell->cell_text = g_strdup ("");
248           text_cell->cell_length = 0;
249         }
250       else
251         {
252           text_cell->cell_text = g_strdup (new_cache);
253           text_cell->cell_length = g_utf8_strlen (new_cache, -1);
254         }
255     }
256
257   g_free (new_cache);
258   gail_text_util_text_setup (text_cell->textutil, text_cell->cell_text);
259   
260   if (rv)
261     {
262       if (emit_change_signal)
263         {
264           g_signal_emit_by_name (cell, "text_changed::insert",
265                                  0, text_cell->cell_length);
266
267           if (obj->name == NULL)
268             g_object_notify (G_OBJECT (obj), "accessible-name");
269         }
270     }
271   return rv;
272 }
273
274 static void
275 atk_text_interface_init (AtkTextIface *iface)
276 {
277   iface->get_text = gail_text_cell_get_text;
278   iface->get_character_at_offset = gail_text_cell_get_character_at_offset;
279   iface->get_text_before_offset = gail_text_cell_get_text_before_offset;
280   iface->get_text_at_offset = gail_text_cell_get_text_at_offset;
281   iface->get_text_after_offset = gail_text_cell_get_text_after_offset;
282   iface->get_character_count = gail_text_cell_get_character_count;
283   iface->get_caret_offset = gail_text_cell_get_caret_offset;
284   iface->set_caret_offset = gail_text_cell_set_caret_offset;
285   iface->get_run_attributes = gail_text_cell_get_run_attributes;
286   iface->get_default_attributes = gail_text_cell_get_default_attributes;
287   iface->get_character_extents = gail_text_cell_get_character_extents;
288   iface->get_offset_at_point = gail_text_cell_get_offset_at_point;
289 }
290
291 static gchar* 
292 gail_text_cell_get_text (AtkText *text, 
293                          gint    start_pos,
294                          gint    end_pos)
295 {
296   if (GAIL_TEXT_CELL (text)->cell_text)
297     return gail_text_util_get_substring (GAIL_TEXT_CELL (text)->textutil,
298               start_pos, end_pos);
299   else
300     return g_strdup ("");
301 }
302
303 static gchar* 
304 gail_text_cell_get_text_before_offset (AtkText         *text,
305                                        gint            offset,
306                                        AtkTextBoundary boundary_type,
307                                        gint            *start_offset,
308                                        gint            *end_offset)
309 {
310   return gail_text_util_get_text (GAIL_TEXT_CELL (text)->textutil,
311         NULL, GAIL_BEFORE_OFFSET, boundary_type, offset, start_offset,
312         end_offset);
313 }
314
315 static gchar* 
316 gail_text_cell_get_text_at_offset (AtkText         *text,
317                                    gint            offset,
318                                    AtkTextBoundary boundary_type,
319                                    gint            *start_offset,
320                                    gint            *end_offset)
321 {
322   return gail_text_util_get_text (GAIL_TEXT_CELL (text)->textutil,
323         NULL, GAIL_AT_OFFSET, boundary_type, offset, start_offset, end_offset);
324 }
325
326 static gchar* 
327 gail_text_cell_get_text_after_offset (AtkText         *text,
328                                       gint            offset,
329                                       AtkTextBoundary boundary_type,
330                                       gint            *start_offset,
331                                       gint            *end_offset)
332 {
333   return gail_text_util_get_text (GAIL_TEXT_CELL (text)->textutil,
334         NULL, GAIL_AFTER_OFFSET, boundary_type, offset, start_offset,
335         end_offset);
336 }
337
338 static gint 
339 gail_text_cell_get_character_count (AtkText *text)
340 {
341   if (GAIL_TEXT_CELL (text)->cell_text != NULL)
342     return GAIL_TEXT_CELL (text)->cell_length;
343   else
344     return 0;
345 }
346
347 static gint 
348 gail_text_cell_get_caret_offset (AtkText *text)
349 {
350   return GAIL_TEXT_CELL (text)->caret_pos;
351 }
352
353 static gboolean 
354 gail_text_cell_set_caret_offset (AtkText *text,
355                                  gint    offset)
356 {
357   GailTextCell *text_cell = GAIL_TEXT_CELL (text);
358
359   if (text_cell->cell_text == NULL)
360     return FALSE;
361   else
362     {
363
364       /* Only set the caret within the bounds and if it is to a new position. */
365       if (offset >= 0 && 
366           offset <= text_cell->cell_length &&
367           offset != text_cell->caret_pos)
368         {
369           text_cell->caret_pos = offset;
370
371           /* emit the signal */
372           g_signal_emit_by_name (text, "text_caret_moved", offset);
373           return TRUE;
374         }
375       else
376         return FALSE;
377     }
378 }
379
380 static AtkAttributeSet*
381 gail_text_cell_get_run_attributes (AtkText *text,
382                                   gint     offset,
383                                   gint     *start_offset,
384                                   gint     *end_offset) 
385 {
386   GailRendererCell *gail_renderer; 
387   GtkCellRendererText *gtk_renderer;
388   AtkAttributeSet *attrib_set = NULL;
389   PangoLayout *layout;
390   AtkObject *parent;
391   GtkWidget *widget;
392
393   gail_renderer = GAIL_RENDERER_CELL (text);
394   gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
395
396   parent = atk_object_get_parent (ATK_OBJECT (text));
397   if (GAIL_IS_CONTAINER_CELL (parent))
398     parent = atk_object_get_parent (parent);
399   g_return_val_if_fail (GAIL_IS_CELL_PARENT (parent), NULL);
400   widget = GTK_ACCESSIBLE (parent)->widget;
401   layout = create_pango_layout (gtk_renderer, widget),
402   attrib_set = gail_misc_layout_get_run_attributes (attrib_set, 
403                                                     layout,
404                                                     gtk_renderer->text,
405                                                     offset,
406                                                     start_offset,
407                                                     end_offset);
408   g_object_unref (G_OBJECT (layout));
409   
410   return attrib_set;
411 }
412
413 static AtkAttributeSet*
414 gail_text_cell_get_default_attributes (AtkText  *text)
415 {
416   GailRendererCell *gail_renderer; 
417   GtkCellRendererText *gtk_renderer;
418   AtkAttributeSet *attrib_set = NULL;
419   PangoLayout *layout;
420   AtkObject *parent;
421   GtkWidget *widget;
422
423   gail_renderer = GAIL_RENDERER_CELL (text);
424   gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
425
426   parent = atk_object_get_parent (ATK_OBJECT (text));
427   if (GAIL_IS_CONTAINER_CELL (parent))
428     parent = atk_object_get_parent (parent);
429   g_return_val_if_fail (GAIL_IS_CELL_PARENT (parent), NULL);
430   widget = GTK_ACCESSIBLE (parent)->widget;
431   layout = create_pango_layout (gtk_renderer, widget),
432
433   attrib_set = gail_misc_get_default_attributes (attrib_set, 
434                                                  layout,
435                                                  widget);
436   g_object_unref (G_OBJECT (layout));
437   return attrib_set;
438 }
439
440 /* 
441  * This function is used by gail_text_cell_get_offset_at_point()
442  * and gail_text_cell_get_character_extents(). There is no 
443  * cached PangoLayout for gailtextcell so we must create a temporary
444  * one using this function.
445  */ 
446 static PangoLayout*
447 create_pango_layout(GtkCellRendererText *gtk_renderer,
448                     GtkWidget           *widget)
449 {
450   PangoAttrList *attr_list;
451   PangoLayout *layout;
452   PangoUnderline uline;
453   PangoFontMask mask;
454
455   layout = gtk_widget_create_pango_layout (widget, gtk_renderer->text);
456
457   if (gtk_renderer->extra_attrs)
458     attr_list = pango_attr_list_copy (gtk_renderer->extra_attrs);
459   else
460     attr_list = pango_attr_list_new ();
461
462   if (gtk_renderer->foreground_set)
463     {
464       PangoColor color;
465       color = gtk_renderer->foreground;
466       add_attr (attr_list, pango_attr_foreground_new (color.red,
467                                                       color.green, color.blue));
468     }
469
470   if (gtk_renderer->strikethrough_set)
471     add_attr (attr_list,
472               pango_attr_strikethrough_new (gtk_renderer->strikethrough));
473
474   mask = pango_font_description_get_set_fields (gtk_renderer->font);
475
476   if (mask & PANGO_FONT_MASK_FAMILY)
477     add_attr (attr_list,
478       pango_attr_family_new (pango_font_description_get_family (gtk_renderer->font)));
479
480   if (mask & PANGO_FONT_MASK_STYLE)
481     add_attr (attr_list, pango_attr_style_new (pango_font_description_get_style (gtk_renderer->font)));
482
483   if (mask & PANGO_FONT_MASK_VARIANT)
484     add_attr (attr_list, pango_attr_variant_new (pango_font_description_get_variant (gtk_renderer->font)));
485
486   if (mask & PANGO_FONT_MASK_WEIGHT)
487     add_attr (attr_list, pango_attr_weight_new (pango_font_description_get_weight (gtk_renderer->font)));
488
489   if (mask & PANGO_FONT_MASK_STRETCH)
490     add_attr (attr_list, pango_attr_stretch_new (pango_font_description_get_stretch (gtk_renderer->font)));
491
492   if (mask & PANGO_FONT_MASK_SIZE)
493     add_attr (attr_list, pango_attr_size_new (pango_font_description_get_size (gtk_renderer->font)));
494
495   if (gtk_renderer->scale_set &&
496       gtk_renderer->font_scale != 1.0)
497     add_attr (attr_list, pango_attr_scale_new (gtk_renderer->font_scale));
498
499   if (gtk_renderer->underline_set)
500     uline = gtk_renderer->underline_style;
501   else
502     uline = PANGO_UNDERLINE_NONE;
503
504   if (uline != PANGO_UNDERLINE_NONE)
505     add_attr (attr_list,
506       pango_attr_underline_new (gtk_renderer->underline_style));
507
508   if (gtk_renderer->rise_set)
509     add_attr (attr_list, pango_attr_rise_new (gtk_renderer->rise));
510
511   pango_layout_set_attributes (layout, attr_list);
512   pango_layout_set_width (layout, -1);
513   pango_attr_list_unref (attr_list);
514
515   return layout;
516 }
517
518 static void 
519 add_attr (PangoAttrList  *attr_list,
520          PangoAttribute *attr)
521 {
522   attr->start_index = 0;
523   attr->end_index = G_MAXINT;
524   pango_attr_list_insert (attr_list, attr);
525 }
526
527 static void      
528 gail_text_cell_get_character_extents (AtkText          *text,
529                                       gint             offset,
530                                       gint             *x,
531                                       gint             *y,
532                                       gint             *width,
533                                       gint             *height,
534                                       AtkCoordType     coords)
535 {
536   GailRendererCell *gail_renderer; 
537   GtkCellRendererText *gtk_renderer;
538   GdkRectangle rendered_rect;
539   GtkWidget *widget;
540   AtkObject *parent;
541   PangoRectangle char_rect;
542   PangoLayout *layout;
543   gint x_offset, y_offset, index, cell_height, cell_width;
544
545   if (!GAIL_TEXT_CELL (text)->cell_text)
546     {
547       *x = *y = *height = *width = 0;
548       return;
549     }
550   if (offset < 0 || offset >= GAIL_TEXT_CELL (text)->cell_length)
551     {
552       *x = *y = *height = *width = 0;
553       return;
554     }
555   gail_renderer = GAIL_RENDERER_CELL (text);
556   gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
557   /*
558    * Thus would be inconsistent with the cache
559    */
560   gail_return_if_fail (gtk_renderer->text);
561
562   parent = atk_object_get_parent (ATK_OBJECT (text));
563   if (GAIL_IS_CONTAINER_CELL (parent))
564     parent = atk_object_get_parent (parent);
565   widget = GTK_ACCESSIBLE (parent)->widget;
566   g_return_if_fail (GAIL_IS_CELL_PARENT (parent));
567   gail_cell_parent_get_cell_area (GAIL_CELL_PARENT (parent), GAIL_CELL (text),
568                                   &rendered_rect);
569
570   gtk_cell_renderer_get_size (GTK_CELL_RENDERER (gtk_renderer), widget,
571     &rendered_rect, &x_offset, &y_offset, &cell_width, &cell_height);
572   layout = create_pango_layout (gtk_renderer, widget);
573
574   index = g_utf8_offset_to_pointer (gtk_renderer->text,
575     offset) - gtk_renderer->text;
576   pango_layout_index_to_pos (layout, index, &char_rect); 
577
578   gail_misc_get_extents_from_pango_rectangle (widget,
579       &char_rect,
580       x_offset + rendered_rect.x + gail_renderer->renderer->xpad,
581       y_offset + rendered_rect.y + gail_renderer->renderer->ypad,
582       x, y, width, height, coords);
583   g_object_unref (layout);
584   return;
585
586
587 static gint      
588 gail_text_cell_get_offset_at_point (AtkText          *text,
589                                     gint             x,
590                                     gint             y,
591                                     AtkCoordType     coords)
592 {
593   AtkObject *parent;
594   GailRendererCell *gail_renderer; 
595   GtkCellRendererText *gtk_renderer;
596   GtkWidget *widget;
597   GdkRectangle rendered_rect;
598   PangoLayout *layout;
599   gint x_offset, y_offset, index;
600  
601   if (!GAIL_TEXT_CELL (text)->cell_text)
602     return -1;
603
604   gail_renderer = GAIL_RENDERER_CELL (text);
605   gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
606   parent = atk_object_get_parent (ATK_OBJECT (text));
607
608   g_return_val_if_fail (gtk_renderer->text, -1);
609   if (GAIL_IS_CONTAINER_CELL (parent))
610     parent = atk_object_get_parent (parent);
611
612   widget = GTK_ACCESSIBLE (parent)->widget;
613
614   g_return_val_if_fail (GAIL_IS_CELL_PARENT (parent), -1);
615   gail_cell_parent_get_cell_area (GAIL_CELL_PARENT (parent), GAIL_CELL (text),
616                                   &rendered_rect);
617   gtk_cell_renderer_get_size (GTK_CELL_RENDERER (gtk_renderer), widget,
618      &rendered_rect, &x_offset, &y_offset, NULL, NULL);
619
620   layout = create_pango_layout (gtk_renderer, widget);
621    
622   index = gail_misc_get_index_at_point_in_layout (widget, layout,
623         x_offset + rendered_rect.x + gail_renderer->renderer->xpad,
624         y_offset + rendered_rect.y + gail_renderer->renderer->ypad,
625         x, y, coords);
626   g_object_unref (layout);
627   if (index == -1)
628     {
629       if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
630         return g_utf8_strlen (gtk_renderer->text, -1);
631     
632       return index;  
633     }
634   else
635     return g_utf8_pointer_to_offset (gtk_renderer->text,
636        gtk_renderer->text + index);  
637 }
638
639 static gunichar 
640 gail_text_cell_get_character_at_offset (AtkText       *text,
641                                         gint          offset)
642 {
643   gchar *index;
644   gchar *string;
645
646   string = GAIL_TEXT_CELL(text)->cell_text;
647
648   if (!string)
649     return '\0';
650
651   if (offset >= g_utf8_strlen (string, -1))
652     return '\0';
653
654   index = g_utf8_offset_to_pointer (string, offset);
655
656   return g_utf8_get_char (index);
657 }
658