ev-history.c 13.5 KB
Newer Older
1 2
/*
 *  Copyright (C) 2005 Marco Pesenti Gritti
3
 *  Copyright (C) 2018 Germán Poo-Caamaño <gpoo@gnome.org>
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
17
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 19 20 21 22 23
 *
 */

#include "config.h"

#include <glib/gi18n.h>
24
#include <string.h>
25 26 27

#include "ev-history.h"

28 29 30
enum {
	CHANGED,
        ACTIVATE_LINK,
31 32 33 34

	N_SIGNALS
};

35 36
#define EV_HISTORY_MAX_LENGTH (32)

37 38
static guint signals[N_SIGNALS] = {0, };

39
typedef struct {
40 41
	GList           *list;
        GList           *current;
42

43 44
        EvDocumentModel *model;
        gulong           page_changed_handler_id;
45 46

        guint            frozen;
47 48 49
} EvHistoryPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (EvHistory, ev_history, G_TYPE_OBJECT)
50

51
#define GET_PRIVATE(o) ev_history_get_instance_private (o);
52

53 54
static void ev_history_set_model (EvHistory       *history,
                                  EvDocumentModel *model);
55 56

static void
57
clear_list (GList *list)
58
{
59 60
        g_list_free_full (list, (GDestroyNotify) g_object_unref);
}
61

62 63 64
static void
ev_history_clear (EvHistory *history)
{
65
	EvHistoryPrivate *priv = GET_PRIVATE (history);
66

67 68 69 70
        clear_list (priv->list);
        priv->list = NULL;

        priv->current = NULL;
71 72
}

73 74 75
static void
ev_history_prune (EvHistory *history)
{
76
	EvHistoryPrivate *priv = GET_PRIVATE (history);
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
        GList *l;
        guint i;

        g_assert (priv->current->next == NULL);

        for (i = 0, l = priv->current; i < EV_HISTORY_MAX_LENGTH && l != NULL; i++, l = l->prev)
                /* empty */;

        if (l == NULL)
                return;

        /* Throw away all history up to @l */
        l = l->next;
        l->prev->next = NULL;
        l->prev = NULL;

        clear_list (priv->list);
        priv->list = l;

        g_assert (g_list_length (priv->list) == EV_HISTORY_MAX_LENGTH);
}

99 100 101 102 103
static void
ev_history_finalize (GObject *object)
{
	EvHistory *history = EV_HISTORY (object);

104 105
        ev_history_clear (history);
        ev_history_set_model (history, NULL);
106

107
	G_OBJECT_CLASS (ev_history_parent_class)->finalize (object);
108 109 110 111 112 113 114 115 116
}

static void
ev_history_class_init (EvHistoryClass *class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (class);

	object_class->finalize = ev_history_finalize;

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
	signals[CHANGED] =
                g_signal_new ("changed",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                              G_STRUCT_OFFSET (EvHistoryClass, changed),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE, 0);

        signals[ACTIVATE_LINK] =
                g_signal_new ("activate-link",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                              G_STRUCT_OFFSET (EvHistoryClass, activate_link),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__OBJECT,
                              G_TYPE_NONE, 1,
                              G_TYPE_OBJECT);
135 136
}

137 138 139 140
static void
ev_history_init (EvHistory *history)
{
}
141

142
gboolean
143 144
ev_history_is_frozen (EvHistory *history)
{
145 146 147
	EvHistoryPrivate *priv = GET_PRIVATE (history);

        return priv->frozen > 0;
148 149
}

150
void
151 152
ev_history_add_link (EvHistory *history,
                     EvLink    *link)
153
{
154 155
        EvHistoryPrivate *priv;

156 157 158
	g_return_if_fail (EV_IS_HISTORY (history));
	g_return_if_fail (EV_IS_LINK (link));

159
        if (ev_history_is_frozen (history))
160 161
                return;

162
	priv = GET_PRIVATE (history);
163

164 165 166 167 168
        if (priv->current) {
                /* Truncate forward history at @current */
                clear_list (priv->current->next);
                priv->current->next = NULL;
        }
169

170 171 172
        /* Push @link to the list */
        priv->current = g_list_append (NULL, g_object_ref (link));
        priv->list = g_list_concat (priv->list, priv->current);
173

174
        ev_history_prune (history);
175 176 177 178 179 180 181

	g_signal_emit (history, signals[CHANGED], 0);
}

static void
ev_history_activate_current_link (EvHistory *history)
{
182 183 184
	EvHistoryPrivate *priv = GET_PRIVATE (history);

        g_assert (priv->current);
185

186
        ev_history_freeze (history);
187 188 189
        g_signal_handler_block (priv->model, priv->page_changed_handler_id);
        g_signal_emit (history, signals[ACTIVATE_LINK], 0, priv->current->data);
        g_signal_handler_unblock (priv->model, priv->page_changed_handler_id);
190
        ev_history_thaw (history);
191 192 193 194 195 196 197

        g_signal_emit (history, signals[CHANGED], 0);
}

gboolean
ev_history_can_go_back (EvHistory *history)
{
198 199
        EvHistoryPrivate *priv;

200
        g_return_val_if_fail (EV_IS_HISTORY (history), FALSE);
201

202 203 204
        if (ev_history_is_frozen (history))
                return FALSE;

205
        priv = GET_PRIVATE (history);
206
        return priv->current && priv->current->prev;
207 208
}

209 210
void
ev_history_go_back (EvHistory *history)
211
{
212 213
        EvHistoryPrivate *priv;

214
        g_return_if_fail (EV_IS_HISTORY (history));
215

216
        if (!ev_history_can_go_back (history))
217
                return;
218

219
        priv = GET_PRIVATE (history);
220 221 222

        /* Move current back one step */
        priv->current = priv->current->prev;
223

224
        ev_history_activate_current_link (history);
225 226
}

227 228
gboolean
ev_history_can_go_forward (EvHistory *history)
229
{
230 231
        EvHistoryPrivate *priv;

232
        g_return_val_if_fail (EV_IS_HISTORY (history), FALSE);
233

234 235 236
        if (ev_history_is_frozen (history))
                return FALSE;

237
        priv = GET_PRIVATE (history);
238
        return priv->current && priv->current->next;
239 240
}

241 242 243
void
ev_history_go_forward (EvHistory *history)
{
244 245
        EvHistoryPrivate *priv;

246 247
        g_return_if_fail (EV_IS_HISTORY (history));

248
        if (!ev_history_can_go_forward (history))
249 250
                return;

251
        priv = GET_PRIVATE (history);
252 253 254

        /* Move current forward one step */
        priv->current = priv->current->next;
255 256 257 258 259 260 261 262 263 264 265 266 267 268

        ev_history_activate_current_link (history);
}

static gint
compare_link (EvLink *a,
              EvLink *b)
{
        if (a == b)
                return 0;

        return ev_link_action_equal (ev_link_get_action (a), ev_link_get_action (b)) ? 0 : 1;
}

269 270 271 272 273 274 275 276 277
/*
 * ev_history_go_to_link:
 * @history: a #EvHistory
 * @link: a #EvLink
 *
 * Goes to the link, if it is in the history.
 *
 * Returns: %TRUE if the link was in the history and history isn't frozen; %FALSE otherwise
 */
278 279 280
gboolean
ev_history_go_to_link (EvHistory *history,
                       EvLink    *link)
281
{
282
        EvHistoryPrivate *priv;
283 284 285 286 287
        GList *l;

        g_return_val_if_fail (EV_IS_HISTORY (history), FALSE);
        g_return_val_if_fail (EV_IS_LINK (link), FALSE);

288
        if (ev_history_is_frozen (history))
289 290
                return FALSE;

291
        priv = GET_PRIVATE (history);
292

293 294 295
        l = g_list_find_custom (priv->list, link, (GCompareFunc) compare_link);
        if (l == NULL)
                return FALSE;
296

297 298
        /* Set the link as current */
        priv->current = l;
299

300
        ev_history_activate_current_link (history);
301

302
        return TRUE;
303
}
304

305 306 307 308 309 310
/**
 * ev_history_get_back_list:
 * @history: a #EvHistory
 *
 * Returns: (transfer container): the back history
 */
311 312 313
GList *
ev_history_get_back_list (EvHistory *history)
{
314 315 316
        EvHistoryPrivate *priv;
        GList *list, *l;

317 318
        g_return_val_if_fail (EV_IS_HISTORY (history), NULL);

319
        priv = GET_PRIVATE (history);
320 321 322 323 324 325 326 327 328

        if (priv->current == NULL)
                return NULL;

        list = NULL;
        for (l = priv->current->prev; l != NULL; l = l->prev)
                list = g_list_prepend (list, l->data);

        return g_list_reverse (list);
329 330
}

331 332 333 334 335 336
/**
 * ev_history_get_forward_list:
 * @history: a #EvHistory
 *
 * Returns: (transfer container): the forward history
 */
337 338 339
GList *
ev_history_get_forward_list (EvHistory *history)
{
340 341
        EvHistoryPrivate *priv;

342 343
        g_return_val_if_fail (EV_IS_HISTORY (history), NULL);

344 345 346
        priv = GET_PRIVATE (history);

        return g_list_copy (priv->current->next);
347 348
}

349 350 351
void
ev_history_freeze (EvHistory *history)
{
352 353
        EvHistoryPrivate *priv;

354 355
        g_return_if_fail (EV_IS_HISTORY (history));

356 357 358
        priv = GET_PRIVATE (history);

        priv->frozen++;
359 360 361 362 363
}

void
ev_history_thaw (EvHistory *history)
{
364 365
        EvHistoryPrivate *priv;

366 367
        g_return_if_fail (EV_IS_HISTORY (history));

368 369 370 371 372
        priv = GET_PRIVATE (history);

        g_return_if_fail (priv->frozen > 0);

        priv->frozen--;
373 374
}

375 376 377
static gint
ev_history_get_current_page (EvHistory *history)
{
378
        EvHistoryPrivate *priv = GET_PRIVATE (history);
379
        EvLink       *link;
380 381 382 383
        EvDocument   *document;
        EvLinkDest   *dest;
        EvLinkAction *action;

384
        if (!priv->current)
385 386
                return -1;

387
        link = priv->current->data;
388
        action = ev_link_get_action (link);
389 390 391 392 393 394 395 396 397
        if (!action)
                return -1;

        dest = ev_link_action_get_dest (action);
        if (!dest)
                return -1;

        switch (ev_link_dest_get_dest_type (dest)) {
        case EV_LINK_DEST_TYPE_NAMED:
398
                document = ev_document_model_get_document (priv->model);
399 400 401 402 403 404 405 406
                if (!EV_IS_DOCUMENT_LINKS (document))
                        return -1;

                return ev_document_links_find_link_page (EV_DOCUMENT_LINKS (document),
                                                         ev_link_dest_get_named_dest (dest));
        case EV_LINK_DEST_TYPE_PAGE_LABEL: {
                gint page = -1;

407
                document = ev_document_model_get_document (priv->model);
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
                ev_document_find_page_by_label (document,
                                                ev_link_dest_get_page_label (dest),
                                                &page);

                return page;
        }
        default:
                return ev_link_dest_get_page (dest);
        }

        return -1;
}

static void
ev_history_add_link_for_page (EvHistory *history,
                              gint       page)
{
425
        EvHistoryPrivate *priv = GET_PRIVATE (history);
426 427 428 429 430 431 432
        EvDocument   *document;
        EvLinkDest   *dest;
        EvLinkAction *action;
        EvLink       *link;
        gchar        *page_label;
        gchar        *title;

433 434 435
        if (ev_history_is_frozen (history))
                return;

436 437 438
        if (ev_history_get_current_page (history) == page)
                return;

439
        document = ev_document_model_get_document (priv->model);
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
        if (!document)
                return;

        page_label = ev_document_get_page_label (document, page);
        if (!page_label)
                return;

        title = g_strdup_printf (_("Page %s"), page_label);
        g_free (page_label);

        dest = ev_link_dest_new_page (page);
        action = ev_link_action_new_dest (dest);
        g_object_unref (dest);

        link = ev_link_new (title, action);
        g_object_unref (action);
        g_free (title);

        ev_history_add_link (history, link);
        g_object_unref (link);
}

static void
page_changed_cb (EvDocumentModel *model,
                 gint             old_page,
                 gint             new_page,
                 EvHistory       *history)
{
        if (ABS (new_page - old_page) > 1)
                ev_history_add_link_for_page (history, new_page);
}

static void
document_changed_cb (EvDocumentModel *model,
                     GParamSpec      *pspec,
                     EvHistory       *history)
{
        ev_history_clear (history);
        ev_history_add_link_for_page (history, ev_document_model_get_page (model));
}

static void
ev_history_set_model (EvHistory       *history,
                      EvDocumentModel *model)
{
485 486 487
        EvHistoryPrivate *priv = GET_PRIVATE (history);

        if (priv->model == model)
488 489
                return;

490 491 492
        if (priv->model) {
                g_object_remove_weak_pointer (G_OBJECT (priv->model),
                                              (gpointer)&priv->model);
493

494 495 496 497
                if (priv->page_changed_handler_id) {
                        g_signal_handler_disconnect (priv->model,
                                                     priv->page_changed_handler_id);
                        priv->page_changed_handler_id = 0;
498 499 500
                }
        }

501
        priv->model = model;
502 503 504 505
        if (!model)
                return;

        g_object_add_weak_pointer (G_OBJECT (model),
506
                                   (gpointer)&priv->model);
507

508
        g_signal_connect (priv->model, "notify::document",
509 510
                          G_CALLBACK (document_changed_cb),
                          history);
511 512
        priv->page_changed_handler_id =
                g_signal_connect (priv->model, "page-changed",
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
                                  G_CALLBACK (page_changed_cb),
                                  history);
}

EvHistory *
ev_history_new (EvDocumentModel *model)
{
        EvHistory *history;

        g_return_val_if_fail (EV_IS_DOCUMENT_MODEL (model), NULL);

        history = EV_HISTORY (g_object_new (EV_TYPE_HISTORY, NULL));
        ev_history_set_model (history, model);

        return history;
}