1
/*
2
 * AT-SPI - Assistive Technology Service Provider Interface
3
 * (Gnome Accessibility Project; https://wiki.gnome.org/Accessibility)
4
 *
5
 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library; if not, write to the
19
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20
 * Boston, MA 02110-1301, USA.
21
 */
22

            
23
#include <atk/atk.h>
24
#include <glib.h>
25
#include <stdio.h>
26
#include <string.h>
27

            
28
#include "my-atk-object.h"
29
#include "my-atk-text.h"
30

            
31
typedef struct _MyAtkTextInfo MyAtkTextInfo;
32

            
33
static void atk_text_interface_init (AtkTextIface *iface);
34

            
35
typedef struct _MyAtkTextSelection MyAtkTextSelection;
36

            
37
struct _MyAtkTextSelection
38
{
39
  gint start;
40
  gint end;
41
};
42

            
43
21
G_DEFINE_TYPE_WITH_CODE (MyAtkText,
44
                         my_atk_text,
45
                         MY_TYPE_ATK_OBJECT,
46
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
47
                                                atk_text_interface_init));
48

            
49
guint
50
41
my_atk_set_text (AtkText *obj,
51
                 const gchar *text,
52
                 const gint x,
53
                 const gint y,
54
                 const gint width,
55
                 const gint height,
56
                 AtkAttributeSet *attrSet)
57
{
58
41
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), -1);
59

            
60
41
  MyAtkText *self = MY_ATK_TEXT (obj);
61
41
  self->text = g_strdup (text);
62
41
  self->x = x;
63
41
  self->y = y;
64
41
  self->width = width;
65
41
  self->height = height;
66
41
  self->attributes = g_slist_copy (attrSet);
67

            
68
41
  return 0;
69
}
70

            
71
MyAtkText *
72
1
my_atk_text_new (void)
73
{
74
1
  return g_object_new (MY_TYPE_ATK_TEXT, NULL);
75
}
76

            
77
static gchar *
78
13
my_atk_text_get_text (AtkText *obj, gint start_offset, gint end_offset)
79
{
80
13
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), NULL);
81
13
  gchar *str = MY_ATK_TEXT (obj)->text;
82

            
83
13
  if ((end_offset < start_offset) || start_offset < 0 || !str)
84
    return NULL;
85
13
  if (strlen (str) < end_offset)
86
    return NULL;
87

            
88
13
  return g_strndup (str + start_offset, end_offset - start_offset);
89
}
90

            
91
static gint
92
1
my_atk_text_get_character_count (AtkText *obj)
93
{
94
1
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), -1);
95
1
  gchar *str = MY_ATK_TEXT (obj)->text;
96
1
  if (!str)
97
    return 0;
98
1
  return (gint) strlen (str);
99
}
100

            
101
static int
102
2
my_atk_text_get_caret_offset (AtkText *obj)
103
{
104
2
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), -1);
105
2
  return MY_ATK_TEXT (obj)->caret_offset;
106
}
107

            
108
static gboolean
109
2
my_atk_text_set_caret_offset (AtkText *obj, gint offset)
110
{
111
2
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), FALSE);
112
2
  MyAtkText *self = MY_ATK_TEXT (obj);
113
2
  if (offset < 0 && strlen (self->text) <= offset)
114
1
    return FALSE;
115
1
  self->caret_offset = offset;
116
1
  return TRUE;
117
}
118

            
119
static gunichar
120
1
my_atk_text_get_character_at_offset (AtkText *obj, gint offset)
121
{
122
1
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), 255);
123
1
  return MY_ATK_TEXT (obj)->text[offset];
124
}
125

            
126
static void
127
1
my_atk_text_get_character_extents (AtkText *obj, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords)
128
{
129
1
  g_return_if_fail (MY_IS_ATK_TEXT (obj));
130
1
  MyAtkText *self = MY_ATK_TEXT (obj);
131
1
  *x = self->x;
132
1
  *y = self->y;
133
1
  *width = self->width;
134
1
  *height = self->height;
135
}
136

            
137
static void
138
1
my_atk_text_get_range_extents (AtkText *obj, gint start_offset, gint stop_offset, AtkCoordType coords, AtkTextRectangle *rect)
139
{
140
1
  g_return_if_fail (MY_IS_ATK_TEXT (obj));
141
1
  MyAtkText *self = MY_ATK_TEXT (obj);
142
1
  rect->x = self->x;
143
1
  rect->y = self->y;
144
1
  rect->width = self->width;
145
1
  rect->height = self->height;
146
}
147

            
148
static gint
149
6
my_atk_text_get_n_selections (AtkText *obj)
150
{
151
6
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), -1);
152
6
  return g_list_length (MY_ATK_TEXT (obj)->selection);
153
}
154

            
155
static gboolean
156
10
my_atk_text_add_selection (AtkText *obj, gint start_offset, gint end_offset)
157
{
158
10
  MyAtkText *self = MY_ATK_TEXT (obj);
159
10
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), FALSE);
160

            
161
10
  MyAtkTextSelection *node = g_malloc (sizeof (MyAtkTextSelection));
162

            
163
10
  node->start = start_offset;
164
10
  node->end = end_offset;
165

            
166
10
  self->selection = g_list_append (self->selection, node);
167

            
168
10
  return TRUE;
169
}
170

            
171
static gchar *
172
4
my_atk_text_get_selection (AtkText *obj, gint selection_num, gint *start_offset, gint *end_offset)
173
{
174
4
  MyAtkText *self = MY_ATK_TEXT (obj);
175
4
  gchar *str = NULL;
176
  GList *it;
177
4
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), NULL);
178

            
179
4
  if (selection_num < 0)
180
    return NULL;
181

            
182
4
  it = g_list_nth (self->selection, selection_num);
183
4
  if (!it)
184
    return NULL;
185

            
186
4
  str = my_atk_text_get_text (obj, ((MyAtkTextSelection *) it->data)->start, ((MyAtkTextSelection *) it->data)->end);
187
4
  if (!str)
188
    return NULL;
189
4
  *start_offset = ((MyAtkTextSelection *) it->data)->start;
190
4
  *end_offset = ((MyAtkTextSelection *) it->data)->end;
191

            
192
4
  return str;
193
}
194

            
195
static gboolean
196
2
my_atk_text_set_selection (AtkText *obj, gint selection_num, gint start_offset, gint end_offset)
197
{
198
2
  MyAtkText *self = MY_ATK_TEXT (obj);
199
2
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), FALSE);
200

            
201
  GList *it;
202

            
203
2
  if (selection_num < 0)
204
    return FALSE;
205

            
206
2
  it = g_list_nth (self->selection, selection_num);
207
2
  if (!it)
208
    return FALSE;
209

            
210
2
  ((MyAtkTextSelection *) it->data)->start = start_offset;
211
2
  ((MyAtkTextSelection *) it->data)->end = end_offset;
212

            
213
2
  return TRUE;
214
}
215

            
216
static gboolean
217
2
my_atk_text_remove_selection (AtkText *obj, gint selection_num)
218
{
219
2
  MyAtkText *self = MY_ATK_TEXT (obj);
220
  GList *it;
221
2
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), FALSE);
222

            
223
2
  if (selection_num < 0)
224
    return FALSE;
225

            
226
2
  it = g_list_nth (self->selection, selection_num);
227
2
  if (!it)
228
    return FALSE;
229

            
230
2
  self->selection = g_list_delete_link (self->selection, it);
231
2
  return TRUE;
232
}
233

            
234
static gint
235
1
my_atk_text_get_offset_at_point (AtkText *obj, gint x, gint y, AtkCoordType coords)
236
{
237
1
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), -1);
238
1
  return 5;
239
}
240

            
241
static AtkAttributeSet *
242
1
my_atk_text_get_default_attributes (AtkText *obj)
243
{
244
1
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), NULL);
245
1
  return MY_ATK_TEXT (obj)->attributes;
246
}
247

            
248
static AtkAttributeSet *
249
4
my_atk_text_get_run_attributes (AtkText *obj, gint offset, gint *start_offset, gint *end_offset)
250
{
251
4
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), NULL);
252
  AtkAttributeSet *attributes;
253
  AtkAttribute *attr;
254

            
255
4
  attr = g_malloc (sizeof (AtkAttribute));
256
4
  attr->name = g_strdup ("text_test_attr1");
257
4
  attr->value = g_strdup ("on");
258
4
  attributes = g_slist_append (NULL, attr);
259

            
260
4
  attr = g_malloc (sizeof (AtkAttribute));
261
4
  attr->name = g_strdup ("text_test_attr2");
262
4
  attr->value = g_strdup ("off");
263
4
  attributes = g_slist_append (attributes, attr);
264

            
265
4
  *start_offset = 5;
266
4
  *end_offset = 10;
267

            
268
4
  return attributes;
269
}
270

            
271
static void
272
2
setSentenceStartEnd (MyAtkText *self, gint *_offset, gint *start_offset, gint *end_offset, const gchar *fstr)
273
{
274
2
  gchar *p_str_begin = NULL;
275
2
  gchar *p_str_end = NULL;
276
2
  const gint length = strlen (self->text);
277
2
  gint offset = *_offset;
278
2
  gint star_correction = 1;
279
  /*
280
   * In case if offset is in the middle of the word rewind to 1 character before.
281
   */
282
9
  for (; g_ascii_isalpha (self->text[offset]) && 0 < offset; offset--)
283
    ;
284
  /*
285
   * if [char]  rewind to word after by passing none alpha
286
   * else  try to find last [string] in range [0,offset]
287
   *   if  found then correct position
288
   *   else not found so this is first sentence find first word
289
   */
290
2
  if (self->text[offset] == fstr[0])
291
    {
292
      for (; !g_ascii_isalpha (self->text[offset]) && offset < length; offset++)
293
        ;
294
      p_str_begin = self->text + offset;
295
    }
296
  else
297
    {
298
2
      p_str_begin = g_strrstr_len (self->text, offset, fstr);
299
2
      if (p_str_begin)
300
        {
301
2
          for (; !g_ascii_isalpha (self->text[offset]) && length < offset; offset++)
302
            ;
303
        }
304
      else
305
        {
306
          for (offset = 0; !g_ascii_isalpha (self->text[offset]) && length < offset; offset++)
307
            ;
308
          star_correction = 0;
309
        }
310
2
      p_str_begin = self->text + offset;
311
    }
312
  /*
313
   * try find ending
314
   * if not found set ending at text end.
315
   * */
316
2
  p_str_end = g_strstr_len (self->text + offset, length - offset, fstr);
317
2
  if (!p_str_end)
318
    {
319
1
      p_str_end = self->text + (length - 1);
320
    }
321
2
  if (p_str_begin && p_str_end)
322
    {
323
2
      *start_offset = p_str_begin - self->text + star_correction;
324
2
      *end_offset = p_str_end - self->text + 1;
325
2
      *_offset = offset;
326
    }
327
2
}
328

            
329
static gchar *
330
5
my_atk_text_get_string_at_offset (AtkText *obj, gint offset, AtkTextGranularity granularity, gint *start_offset, gint *end_offset)
331
{
332
5
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), NULL);
333
5
  MyAtkText *self = MY_ATK_TEXT (obj);
334
  gint cnt;
335
  gint length;
336
5
  gint myoffset = 0;
337
5
  *start_offset = -1;
338
5
  *end_offset = -1;
339

            
340
5
  switch (granularity)
341
    {
342
1
    case ATK_TEXT_GRANULARITY_CHAR:
343
1
      *start_offset = offset;
344
1
      *end_offset = *start_offset + 1;
345
1
      break;
346
1
    case ATK_TEXT_GRANULARITY_WORD:
347
1
      length = strlen (self->text);
348
2
      for (; !g_ascii_isalpha (self->text[offset]) && offset < length; offset++)
349
        ;
350
3
      for (cnt = offset; cnt < length; cnt++)
351
        {
352
3
          if (!g_ascii_isalpha (self->text[cnt]))
353
            {
354
1
              *start_offset = offset;
355
1
              *end_offset = cnt - 1;
356
1
              myoffset = 1;
357
1
              break;
358
            }
359
        }
360
2
      for (cnt = offset; 0 < cnt; cnt--)
361
        {
362
2
          if (!g_ascii_isalpha (self->text[cnt]))
363
            {
364
1
              *start_offset = cnt + 1;
365
1
              break;
366
            }
367
        }
368
1
      break;
369
1
    case ATK_TEXT_GRANULARITY_SENTENCE:
370
1
      setSentenceStartEnd (self, &offset, start_offset, end_offset, ".");
371
1
      break;
372
1
    case ATK_TEXT_GRANULARITY_LINE:
373
1
      setSentenceStartEnd (self, &offset, start_offset, end_offset, "/n");
374
1
      break;
375
1
    case ATK_TEXT_GRANULARITY_PARAGRAPH:
376
      /* Not implemented */
377
1
      *start_offset = 0;
378
1
      *end_offset = 0;
379
1
      break;
380
    default:
381
      break;
382
    }
383
5
  return my_atk_text_get_text (obj, *start_offset, *end_offset + myoffset);
384
}
385

            
386
AtkTextRange **
387
1
my_atk_get_bounded_ranges (AtkText *obj, AtkTextRectangle *rect, AtkCoordType ctype, AtkTextClipType xclip, AtkTextClipType yclip)
388
{
389
1
  g_return_val_if_fail (MY_IS_ATK_TEXT (obj), NULL);
390
1
  AtkTextRange **range = g_new (AtkTextRange *, 3);
391

            
392
1
  *range = g_new (AtkTextRange, 1);
393
1
  (*range)->start_offset = 0;
394
1
  (*range)->end_offset = 5;
395
1
  (*range)->content = my_atk_text_get_text (obj, (*range)->start_offset, (*range)->end_offset);
396

            
397
1
  *(range + 1) = g_new (AtkTextRange, 1);
398
1
  (*(range + 1))->start_offset = 6;
399
1
  (*(range + 1))->end_offset = 10;
400
1
  (*(range + 1))->content = my_atk_text_get_text (obj, (*(range + 1))->start_offset, (*(range + 1))->end_offset);
401

            
402
1
  *(range + 2) = NULL;
403

            
404
1
  return range;
405
}
406

            
407
static void
408
21
atk_text_interface_init (AtkTextIface *iface)
409
{
410
21
  if (!iface)
411
    return;
412

            
413
21
  iface->get_text = my_atk_text_get_text;
414
21
  iface->get_character_count = my_atk_text_get_character_count;
415
21
  iface->get_caret_offset = my_atk_text_get_caret_offset;
416
21
  iface->set_caret_offset = my_atk_text_set_caret_offset;
417
21
  iface->get_character_at_offset = my_atk_text_get_character_at_offset;
418
21
  iface->get_character_extents = my_atk_text_get_character_extents;
419
21
  iface->get_range_extents = my_atk_text_get_range_extents;
420
21
  iface->get_n_selections = my_atk_text_get_n_selections;
421
21
  iface->add_selection = my_atk_text_add_selection;
422
21
  iface->get_selection = my_atk_text_get_selection;
423
21
  iface->set_selection = my_atk_text_set_selection;
424
21
  iface->remove_selection = my_atk_text_remove_selection;
425
21
  iface->get_offset_at_point = my_atk_text_get_offset_at_point;
426
21
  iface->get_default_attributes = my_atk_text_get_default_attributes;
427
21
  iface->get_string_at_offset = my_atk_text_get_string_at_offset;
428
21
  iface->get_bounded_ranges = my_atk_get_bounded_ranges;
429
21
  iface->get_run_attributes = my_atk_text_get_run_attributes;
430
}
431

            
432
static void
433
41
my_atk_text_init (MyAtkText *self)
434
{
435
41
  self->text = NULL;
436
41
  self->caret_offset = -1;
437
41
  self->x = -1;
438
41
  self->y = -1;
439
41
  self->width = -1;
440
41
  self->height = -1;
441
41
  self->selection = NULL;
442
41
  self->attributes = NULL;
443
41
}
444

            
445
static void
446
my_atk_text_class_initialize (AtkObject *obj, gpointer data)
447
{
448
}
449

            
450
static void
451
my_atk_text_class_finalize (GObject *obj)
452
{
453
}
454

            
455
static void
456
21
my_atk_text_class_init (MyAtkTextClass *my_class)
457
{
458
21
  AtkObjectClass *atk_class = ATK_OBJECT_CLASS (my_class);
459
21
  GObjectClass *gobject_class = G_OBJECT_CLASS (my_class);
460

            
461
21
  gobject_class->finalize = my_atk_text_class_finalize;
462
21
  atk_class->initialize = my_atk_text_class_initialize;
463
21
}