Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ev-view-accessible.c
Go to the documentation of this file.
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /* this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2004 Red Hat, Inc
5  *
6  * Evince is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Evince is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include <math.h>
22 #include <config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gtk/gtk.h>
25 
26 #include "ev-selection.h"
27 #include "ev-page-cache.h"
28 #include "ev-view-accessible.h"
29 #include "ev-view-private.h"
30 #include "ev-page-accessible.h"
31 
32 static void ev_view_accessible_action_iface_init (AtkActionIface *iface);
33 static void ev_view_accessible_document_iface_init (AtkDocumentIface *iface);
34 
35 enum {
39 };
40 
41 static const gchar *const ev_view_accessible_action_names[] =
42 {
43  N_("Scroll Up"),
44  N_("Scroll Down"),
45  NULL
46 };
47 
48 static const gchar *const ev_view_accessible_action_descriptions[] =
49 {
50  N_("Scroll View Up"),
51  N_("Scroll View Down"),
52  NULL
53 };
54 
57 
58  /* AtkAction */
61  GtkScrollType idle_scroll;
62 
64  gint start_page;
65  gint end_page;
66  AtkObject *focused_element;
67 
68  GPtrArray *children;
69 };
70 
71 G_DEFINE_TYPE_WITH_CODE (EvViewAccessible, ev_view_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE,
72  G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, ev_view_accessible_action_iface_init)
73  G_IMPLEMENT_INTERFACE (ATK_TYPE_DOCUMENT, ev_view_accessible_document_iface_init)
74  )
75 
76 static gint
77 get_relevant_page (EvView *view)
78 {
79  return ev_view_is_caret_navigation_enabled (view) ? view->cursor_page : view->current_page;
80 }
81 
82 static void
84 {
85  gint i;
86  AtkObject *child;
87 
88  if (self->priv->children == NULL)
89  return;
90 
91  for (i = 0; i < self->priv->children->len; i++) {
92  child = g_ptr_array_index (self->priv->children, i);
93  atk_object_notify_state_change (child, ATK_STATE_DEFUNCT, TRUE);
94  }
95 
96  g_clear_pointer (&self->priv->children, g_ptr_array_unref);
97 }
98 
99 static void
101 {
102  EvViewAccessiblePrivate *priv = EV_VIEW_ACCESSIBLE (object)->priv;
103  int i;
104 
105  if (priv->model) {
106  g_signal_handlers_disconnect_by_data (priv->model, object);
107  g_object_unref (priv->model);
108  priv->model = NULL;
109  }
110  if (priv->action_idle_handler)
111  g_source_remove (priv->action_idle_handler);
112  for (i = 0; i < LAST_ACTION; i++)
113  g_free (priv->action_descriptions [i]);
114 
116 
117  G_OBJECT_CLASS (ev_view_accessible_parent_class)->finalize (object);
118 }
119 
120 static void
122  gpointer data)
123 {
124  if (ATK_OBJECT_CLASS (ev_view_accessible_parent_class)->initialize != NULL)
125  ATK_OBJECT_CLASS (ev_view_accessible_parent_class)->initialize (obj, data);
126 
127  gtk_accessible_set_widget (GTK_ACCESSIBLE (obj), GTK_WIDGET (data));
128 
129  atk_object_set_name (obj, _("Document View"));
130  atk_object_set_role (obj, ATK_ROLE_DOCUMENT_FRAME);
131 }
132 
133 gint
135 {
136  return self->priv->children == NULL ? 0 : self->priv->children->len;
137 }
138 
139 static AtkObject *
141  gint i)
142 {
143  EvViewAccessible *self;
144  EvView *view;
145 
146  g_return_val_if_fail (EV_IS_VIEW_ACCESSIBLE (obj), NULL);
147  self = EV_VIEW_ACCESSIBLE (obj);
148  g_return_val_if_fail (i >= 0 || i < ev_view_accessible_get_n_pages (self), NULL);
149 
150  view = EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (obj)));
151  if (view == NULL)
152  return NULL;
153 
154  /* If a given page is requested, we assume that the text would
155  * be requested soon, so we need to be sure that is cached.*/
156  if (view->page_cache)
158 
159  return g_object_ref (g_ptr_array_index (self->priv->children, i));
160 }
161 
162 static gint
164 {
166 }
167 
168 static void
170 {
171  GObjectClass *object_class = G_OBJECT_CLASS (klass);
172  AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
173 
174  object_class->finalize = ev_view_accessible_finalize;
175  atk_class->initialize = ev_view_accessible_initialize;
176  atk_class->get_n_children = ev_view_accessible_get_n_children;
177  atk_class->ref_child = ev_view_accessible_ref_child;
178 
179  g_type_class_add_private (klass, sizeof (EvViewAccessiblePrivate));
180 }
181 
182 static void
184 {
185  accessible->priv = G_TYPE_INSTANCE_GET_PRIVATE (accessible, EV_TYPE_VIEW_ACCESSIBLE, EvViewAccessiblePrivate);
186 }
187 
188 #if ATK_CHECK_VERSION (2, 11, 3)
189 static gint
190 ev_view_accessible_get_page_count (AtkDocument *atk_document)
191 {
192  g_return_val_if_fail (EV_IS_VIEW_ACCESSIBLE (atk_document), -1);
193 
194  return ev_view_accessible_get_n_pages (EV_VIEW_ACCESSIBLE (atk_document));
195 }
196 
197 static gint
198 ev_view_accessible_get_current_page_number (AtkDocument *atk_document)
199 {
200  GtkWidget *widget;
201 
202  g_return_val_if_fail (EV_IS_VIEW_ACCESSIBLE (atk_document), -1);
203 
204  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_document));
205  if (widget == NULL)
206  return -1;
207 
208  /* +1 as user starts to count on 1, but evince starts on 0 */
209  return get_relevant_page (EV_VIEW (widget)) + 1;
210 }
211 #endif
212 
213 static void
214 ev_view_accessible_document_iface_init (AtkDocumentIface *iface)
215 {
216 #if ATK_CHECK_VERSION (2, 11, 3)
217  iface->get_current_page_number = ev_view_accessible_get_current_page_number;
218  iface->get_page_count = ev_view_accessible_get_page_count;
219 #endif
220 }
221 
222 static gboolean
224 {
225  EvViewAccessiblePrivate* priv = EV_VIEW_ACCESSIBLE (data)->priv;
226 
227  ev_view_scroll (EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (data))),
228  priv->idle_scroll,
229  FALSE);
230  priv->action_idle_handler = 0;
231  return FALSE;
232 }
233 
234 static gboolean
236  gint i)
237 {
238  EvViewAccessiblePrivate* priv = EV_VIEW_ACCESSIBLE (action)->priv;
239 
240  if (gtk_accessible_get_widget (GTK_ACCESSIBLE (action)) == NULL)
241  return FALSE;
242 
243  if (priv->action_idle_handler)
244  return FALSE;
245 
246  switch (i) {
247  case ACTION_SCROLL_UP:
248  priv->idle_scroll = GTK_SCROLL_PAGE_BACKWARD;
249  break;
250  case ACTION_SCROLL_DOWN:
251  priv->idle_scroll = GTK_SCROLL_PAGE_FORWARD;
252  break;
253  default:
254  return FALSE;
255  }
257  action);
258  return TRUE;
259 }
260 
261 static gint
263 {
264  return LAST_ACTION;
265 }
266 
267 static const gchar *
269  gint i)
270 {
271  EvViewAccessiblePrivate* priv = EV_VIEW_ACCESSIBLE (action)->priv;
272 
273  if (i < 0 || i >= LAST_ACTION)
274  return NULL;
275 
276  if (priv->action_descriptions[i])
277  return priv->action_descriptions[i];
278  else
280 }
281 
282 static const gchar *
284  gint i)
285 {
286  if (i < 0 || i >= LAST_ACTION)
287  return NULL;
288 
290 }
291 
292 static gboolean
294  gint i,
295  const gchar *description)
296 {
297  EvViewAccessiblePrivate* priv = EV_VIEW_ACCESSIBLE (action)->priv;
298  gchar *old_description;
299 
300  if (i < 0 || i >= LAST_ACTION)
301  return FALSE;
302 
303  old_description = priv->action_descriptions[i];
304  priv->action_descriptions[i] = g_strdup (description);
305  g_free (old_description);
306 
307  return TRUE;
308 }
309 
310 static void
311 ev_view_accessible_action_iface_init (AtkActionIface * iface)
312 {
313  iface->do_action = ev_view_accessible_action_do_action;
314  iface->get_n_actions = ev_view_accessible_action_get_n_actions;
315  iface->get_description = ev_view_accessible_action_get_description;
316  iface->get_name = ev_view_accessible_action_get_name;
317  iface->set_description = ev_view_accessible_action_set_description;
318 }
319 
320 static void
322  gint page,
323  gint offset,
324  EvViewAccessible *accessible)
325 {
326  EvViewAccessiblePrivate* priv = accessible->priv;
327  EvPageAccessible *page_accessible = NULL;
328 
329  if (priv->previous_cursor_page != page) {
330  AtkObject *previous_page = NULL;
331  AtkObject *current_page = NULL;
332 
333  previous_page = g_ptr_array_index (priv->children,
334  priv->previous_cursor_page);
335  atk_object_notify_state_change (previous_page, ATK_STATE_FOCUSED, FALSE);
336  priv->previous_cursor_page = page;
337  current_page = g_ptr_array_index (priv->children, page);
338  atk_object_notify_state_change (current_page, ATK_STATE_FOCUSED, TRUE);
339 
340 #if ATK_CHECK_VERSION (2, 11, 2)
341  /* +1 as user start to count on 1, but evince starts on 0 */
342  g_signal_emit_by_name (accessible, "page-changed", page + 1);
343 #endif
344  }
345 
346  page_accessible = g_ptr_array_index (priv->children, page);
347  g_signal_emit_by_name (page_accessible, "text-caret-moved", offset);
348 }
349 
350 static void
352  EvViewAccessible *view_accessible)
353 {
354  AtkObject *page_accessible;
355 
356  page_accessible = g_ptr_array_index (view_accessible->priv->children,
357  get_relevant_page (view));
358  g_signal_emit_by_name (page_accessible, "text-selection-changed");
359 }
360 
361 static void
363  gint old_page,
364  gint new_page,
365  EvViewAccessible *accessible)
366 {
367 #if ATK_CHECK_VERSION (2, 11, 2)
368  EvView *view;
369 
370  view = EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
372  g_signal_emit_by_name (accessible, "page-changed", new_page + 1);
373 #endif
374 }
375 
376 static void
378 {
379  gint i;
380  EvPageAccessible *child;
381  gint n_pages;
382  EvDocument *ev_document;
383 
384  ev_document = ev_document_model_get_document (self->priv->model);
385  n_pages = ev_document_get_n_pages (ev_document);
386 
387  self->priv->children = g_ptr_array_new_full (n_pages, (GDestroyNotify) g_object_unref);
388  for (i = 0; i < n_pages; i++) {
389  child = ev_page_accessible_new (self, i);
390  g_ptr_array_add (self->priv->children, child);
391  }
392 
393  /* When a document is reloaded, it may have less pages.
394  * We need to update the end page accordingly to avoid
395  * invalid access to self->priv->children
396  * See https://bugzilla.gnome.org/show_bug.cgi?id=735744
397  */
398  if (self->priv->end_page >= n_pages)
399  self->priv->end_page = n_pages - 1;
400 }
401 
402 static void
404  GParamSpec *pspec,
405  EvViewAccessible *accessible)
406 {
407  EvDocument *document = ev_document_model_get_document (model);
408 
409  clear_children (accessible);
410 
411  if (document == NULL)
412  return;
413 
414  initialize_children (accessible);
415 
416  /* Inside this callback the document is already loaded. We
417  * don't have here an "just before" and "just after"
418  * signal. We emit both in a row, as usual ATs uses reload to
419  * know that current content has changed, and load-complete to
420  * know that the content is already available.
421  */
422  g_signal_emit_by_name (accessible, "reload");
423  g_signal_emit_by_name (accessible, "load-complete");
424 }
425 
426 void
428  EvDocumentModel *model)
429 {
430  EvViewAccessiblePrivate* priv = accessible->priv;
431 
432  if (priv->model == model)
433  return;
434 
435  if (priv->model) {
436  g_signal_handlers_disconnect_by_data (priv->model, accessible);
437  g_object_unref (priv->model);
438  }
439 
440  priv->model = g_object_ref (model);
441 
442  document_changed_cb (model, NULL, accessible);
443  g_signal_connect (priv->model, "page-changed",
444  G_CALLBACK (page_changed_cb),
445  accessible);
446  g_signal_connect (priv->model, "notify::document",
447  G_CALLBACK (document_changed_cb),
448  accessible);
449 }
450 
451 static gboolean
453  GdkEventFocus *event,
454  EvViewAccessible *self)
455 {
456  AtkObject *page_accessible;
457 
458  g_return_val_if_fail (EV_IS_VIEW (widget), FALSE);
459  g_return_val_if_fail (EV_IS_VIEW_ACCESSIBLE (self), FALSE);
460 
461  if (self->priv->children == NULL || self->priv->children->len == 0)
462  return FALSE;
463 
464  page_accessible = g_ptr_array_index (self->priv->children,
465  get_relevant_page (EV_VIEW (widget)));
466  atk_object_notify_state_change (page_accessible,
467  ATK_STATE_FOCUSED, event->in);
468 
469  return FALSE;
470 }
471 
472 AtkObject *
473 ev_view_accessible_new (GtkWidget *widget)
474 {
475  AtkObject *accessible;
476  EvView *view;
477 
478  g_return_val_if_fail (EV_IS_VIEW (widget), NULL);
479 
480  accessible = g_object_new (EV_TYPE_VIEW_ACCESSIBLE, NULL);
481  atk_object_initialize (accessible, widget);
482 
483  g_signal_connect (widget, "cursor-moved",
484  G_CALLBACK (ev_view_accessible_cursor_moved),
485  accessible);
486  g_signal_connect (widget, "selection-changed",
488  accessible);
489  g_signal_connect (widget, "focus-in-event",
491  accessible);
492  g_signal_connect (widget, "focus-out-event",
494  accessible);
495 
496  view = EV_VIEW (widget);
497  if (view->model)
499  view->model);
500 
501  return accessible;
502 }
503 
504 gint
506 {
507  EvView *view;
508 
509  g_return_val_if_fail (EV_IS_VIEW_ACCESSIBLE (accessible), -1);
510 
511  view = EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
512 
513  return get_relevant_page (view);
514 }
515 
516 void
518  gint page,
519  EvRectangle *doc_rect,
520  EvRectangle *atk_rect,
521  AtkCoordType coord_type)
522 {
523  EvView *view;
524  GdkRectangle view_rect;
525  GtkWidget *widget, *toplevel;
526  gint x_widget, y_widget;
527 
528  view = EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
529  _ev_view_transform_doc_rect_to_view_rect (view, page, doc_rect, &view_rect);
530  view_rect.x -= view->scroll_x;
531  view_rect.y -= view->scroll_y;
532 
533  widget = GTK_WIDGET (view);
534  toplevel = gtk_widget_get_toplevel (widget);
535  gtk_widget_translate_coordinates (widget, toplevel, 0, 0, &x_widget, &y_widget);
536  view_rect.x += x_widget;
537  view_rect.y += y_widget;
538 
539  if (coord_type == ATK_XY_SCREEN) {
540  gint x_window, y_window;
541  gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window);
542  view_rect.x += x_window;
543  view_rect.y += y_window;
544  }
545 
546  atk_rect->x1 = view_rect.x;
547  atk_rect->y1 = view_rect.y;
548  atk_rect->x2 = view_rect.x + view_rect.width;
549  atk_rect->y2 = view_rect.y + view_rect.height;
550 }
551 
552 gboolean
554  gint page,
555  EvRectangle *doc_rect)
556 {
557  EvView *view;
558  GdkRectangle view_rect;
559  GtkAllocation allocation;
560  gint x, y;
561  gboolean hidden;
562 
563  view = EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
564  if (page < view->start_page || page > view->end_page)
565  return FALSE;
566 
567  gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
568  x = gtk_adjustment_get_value (view->hadjustment);
569  y = gtk_adjustment_get_value (view->vadjustment);
570 
571  _ev_view_transform_doc_rect_to_view_rect (view, page, doc_rect, &view_rect);
572  hidden = view_rect.x + view_rect.width < x || view_rect.x > x + allocation.width ||
573  view_rect.y + view_rect.height < y || view_rect.y > y + allocation.height;
574 
575  return !hidden;
576 }
577 
578 void
580  gint start,
581  gint end)
582 {
583  gint i;
584  AtkObject *page;
585 
586  g_return_if_fail (EV_IS_VIEW_ACCESSIBLE (accessible));
587 
588  for (i = accessible->priv->start_page; i <= accessible->priv->end_page; i++) {
589  if (i < start || i > end) {
590  page = g_ptr_array_index (accessible->priv->children, i);
591  atk_object_notify_state_change (page, ATK_STATE_SHOWING, FALSE);
592  }
593  }
594 
595  for (i = start; i <= end; i++) {
596  if (i < accessible->priv->start_page || i > accessible->priv->end_page) {
597  page = g_ptr_array_index (accessible->priv->children, i);
598  atk_object_notify_state_change (page, ATK_STATE_SHOWING, TRUE);
599  }
600  }
601 
602  accessible->priv->start_page = start;
603  accessible->priv->end_page = end;
604 }
605 
606 void
608  EvMapping *new_focus,
609  gint new_focus_page)
610 {
611  EvPageAccessible *page;
612 
613  if (accessible->priv->focused_element) {
614  atk_object_notify_state_change (accessible->priv->focused_element, ATK_STATE_FOCUSED, FALSE);
615  accessible->priv->focused_element = NULL;
616  }
617 
618  if (!new_focus || new_focus_page == -1)
619  return;
620 
621  page = g_ptr_array_index (accessible->priv->children, new_focus_page);
622  accessible->priv->focused_element = ev_page_accessible_get_accessible_for_mapping (page, new_focus);
623  if (accessible->priv->focused_element)
624  atk_object_notify_state_change (accessible->priv->focused_element, ATK_STATE_FOCUSED, TRUE);
625 }
626 
627 void
629  EvMapping *element,
630  gint element_page)
631 {
632  EvPageAccessible *page;
633 
634  page = g_ptr_array_index (accessible->priv->children, element_page);
636 }