image_list.c 27.5 KB
Newer Older
1
/*
2
 *  Copyright (c) Stephan Arts 2009-2011 <stephan@xfce.org>
3 4 5 6 7
 *
 *  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 of the License, or
 *  (at your option) any later version.
Stephan Arts's avatar
Stephan Arts committed
8 9
 *
 *  This program is distributed in the hope that it will be useful,
10 11 12 13 14 15 16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library 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
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Stephan Arts's avatar
Stephan Arts committed
17 18
 *
 *  Sorting-algorithm taken from the thunar filemanager.
19 20 21 22 23 24 25
 */

#include <config.h>

#include <gtk/gtk.h>
#include <gtk/gtkmarshal.h>

26
#include <stdlib.h>
27 28 29 30
#include <string.h>

#include <libexif/exif-data.h>

Stephan Arts's avatar
Stephan Arts committed
31
#include "util.h"
Stephan Arts's avatar
Stephan Arts committed
32
#include "file.h"
33
#include "image_list.h"
34
#include "settings.h"
35 36 37 38 39 40 41 42

static void 
rstto_image_list_init(RsttoImageList *);
static void
rstto_image_list_class_init(RsttoImageListClass *);
static void
rstto_image_list_dispose(GObject *object);

Stephan Arts's avatar
Stephan Arts committed
43 44 45 46 47 48 49 50
static void
cb_file_monitor_changed (
        GFileMonitor      *monitor,
        GFile             *file,
        GFile             *other_file,
        GFileMonitorEvent  event_type,
        gpointer           user_data );

51 52 53 54 55 56 57
static void 
rstto_image_list_iter_init(RsttoImageListIter *);
static void
rstto_image_list_iter_class_init(RsttoImageListIterClass *);
static void
rstto_image_list_iter_dispose(GObject *object);

58 59 60 61 62 63
static void
cb_rstto_wrap_images_changed (
        GObject *settings,
        GParamSpec *pspec,
        gpointer user_data);

64 65 66 67 68 69 70 71 72
static void
rstto_image_list_monitor_dir (
        RsttoImageList *image_list,
        GFile *dir );

static void
rstto_image_list_remove_all (
        RsttoImageList *image_list);

73 74 75 76
static gboolean
iter_next (
        RsttoImageListIter *iter,
        gboolean sticky);
77

78 79 80 81
static gboolean
iter_previous (
        RsttoImageListIter *iter,
        gboolean sticky);
82

83 84 85 86 87 88
static void 
iter_set_position (
        RsttoImageListIter *iter,
        gint pos,
        gboolean sticky);

89 90 91
static RsttoImageListIter * rstto_image_list_iter_new ();

static gint
Stephan Arts's avatar
Stephan Arts committed
92
cb_rstto_image_list_image_name_compare_func (RsttoFile *a, RsttoFile *b);
Stephan Arts's avatar
Stephan Arts committed
93
static gint
Stephan Arts's avatar
Stephan Arts committed
94
cb_rstto_image_list_exif_date_compare_func (RsttoFile *a, RsttoFile *b);
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

static GObjectClass *parent_class = NULL;
static GObjectClass *iter_parent_class = NULL;

enum
{
    RSTTO_IMAGE_LIST_SIGNAL_NEW_IMAGE = 0,
    RSTTO_IMAGE_LIST_SIGNAL_REMOVE_IMAGE,
    RSTTO_IMAGE_LIST_SIGNAL_REMOVE_ALL,
    RSTTO_IMAGE_LIST_SIGNAL_COUNT
};

enum
{
    RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED = 0,
110
    RSTTO_IMAGE_LIST_ITER_SIGNAL_PREPARE_CHANGE,
111 112 113 114 115 116
    RSTTO_IMAGE_LIST_ITER_SIGNAL_COUNT
};

struct _RsttoImageListIterPriv
{
    RsttoImageList *image_list;
117 118 119 120
    RsttoFile      *file;
    
    /* This is set if the iter-position is chosen by the user */
    gboolean        sticky; 
121 122 123 124
};

struct _RsttoImageListPriv
{
Stephan Arts's avatar
Stephan Arts committed
125
    GFileMonitor *monitor;
126
    RsttoSettings *settings;
127
    GtkFileFilter *filter;
Stephan Arts's avatar
Stephan Arts committed
128

Stephan Arts's avatar
Stephan Arts committed
129 130 131 132 133
    GList        *images;
    gint          n_images;

    GSList       *iterators;
    GCompareFunc  cb_rstto_image_list_compare_func;
134 135

    gboolean      wrap_images;
136 137 138 139 140 141
};

static gint rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_COUNT];
static gint rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_COUNT];

GType
Stephan Arts's avatar
Stephan Arts committed
142
rstto_image_list_get_type (void)
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
{
    static GType rstto_image_list_type = 0;

    if (!rstto_image_list_type)
    {
        static const GTypeInfo rstto_image_list_info = 
        {
            sizeof (RsttoImageListClass),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) rstto_image_list_class_init,
            (GClassFinalizeFunc) NULL,
            NULL,
            sizeof (RsttoImageList),
            0,
            (GInstanceInitFunc) rstto_image_list_init,
            NULL
        };

        rstto_image_list_type = g_type_register_static (G_TYPE_OBJECT, "RsttoImageList", &rstto_image_list_info, 0);
    }
    return rstto_image_list_type;
}

static void
rstto_image_list_init(RsttoImageList *image_list)
{
170

171
    image_list->priv = g_new0 (RsttoImageListPriv, 1);
172
    image_list->priv->settings = rstto_settings_new ();
173 174 175
    image_list->priv->filter = gtk_file_filter_new ();
    g_object_ref_sink (image_list->priv->filter);
    gtk_file_filter_add_pixbuf_formats (image_list->priv->filter);
176

Stephan Arts's avatar
Stephan Arts committed
177
    image_list->priv->cb_rstto_image_list_compare_func = (GCompareFunc)cb_rstto_image_list_image_name_compare_func;
178 179 180 181 182 183 184 185 186 187 188

    image_list->priv->wrap_images = rstto_settings_get_boolean_property (
            image_list->priv->settings,
            "wrap-images");

    g_signal_connect (
            G_OBJECT(image_list->priv->settings),
            "notify::wrap-images",
            G_CALLBACK (cb_rstto_wrap_images_changed),
            image_list);

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
}

static void
rstto_image_list_class_init(RsttoImageListClass *nav_class)
{
    GObjectClass *object_class = G_OBJECT_CLASS(nav_class);

    parent_class = g_type_class_peek_parent(nav_class);

    object_class->dispose = rstto_image_list_dispose;

    rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_NEW_IMAGE] = g_signal_new("new-image",
            G_TYPE_FROM_CLASS(nav_class),
            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
            0,
            NULL,
            NULL,
            g_cclosure_marshal_VOID__OBJECT,
            G_TYPE_NONE,
            1,
            G_TYPE_OBJECT,
            NULL);

    rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_REMOVE_IMAGE] = g_signal_new("remove-image",
            G_TYPE_FROM_CLASS(nav_class),
            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
            0,
            NULL,
            NULL,
            g_cclosure_marshal_VOID__OBJECT,
            G_TYPE_NONE,
            1,
            G_TYPE_OBJECT,
            NULL);

    rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_REMOVE_ALL] = g_signal_new("remove-all",
            G_TYPE_FROM_CLASS(nav_class),
            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
            0,
            NULL,
            NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE,
            0,
            NULL);
}

static void
rstto_image_list_dispose(GObject *object)
{
239 240 241 242 243 244 245 246
    RsttoImageList *image_list = RSTTO_IMAGE_LIST(object);
    if (NULL != image_list->priv)
    {
        if (image_list->priv->settings)
        {
            g_object_unref (image_list->priv->settings);
            image_list->priv->settings = NULL;
        }
247 248 249 250 251 252

        if (image_list->priv->filter)
        {
            g_object_unref (image_list->priv->filter);
            image_list->priv->filter= NULL;
        }
253 254 255 256
        
        g_free (image_list->priv);
        image_list->priv = NULL;
    }
257 258 259
}

RsttoImageList *
Stephan Arts's avatar
Stephan Arts committed
260
rstto_image_list_new (void)
261 262 263 264 265 266 267 268 269
{
    RsttoImageList *image_list;

    image_list = g_object_new(RSTTO_TYPE_IMAGE_LIST, NULL);

    return image_list;
}

gboolean
270 271 272 273
rstto_image_list_add_file (
        RsttoImageList *image_list,
        RsttoFile *file,
        GError **error )
274
{
275
    GtkFileFilterInfo filter_info;
Stephan Arts's avatar
Stephan Arts committed
276
    GList *image_iter = g_list_find (image_list->priv->images, file);
277
    GSList *iter = image_list->priv->iterators;
278

279 280 281
    g_return_val_if_fail ( NULL != file , FALSE);
    g_return_val_if_fail ( RSTTO_IS_FILE (file) , FALSE);

Stephan Arts's avatar
Stephan Arts committed
282 283
    if (!image_iter)
    {
284
        if (file)
Stephan Arts's avatar
Stephan Arts committed
285
        {
286 287 288
            filter_info.contains =  GTK_FILE_FILTER_MIME_TYPE | GTK_FILE_FILTER_URI;
            filter_info.uri = rstto_file_get_uri (file);
            filter_info.mime_type = rstto_file_get_content_type (file);
Stephan Arts's avatar
Stephan Arts committed
289

290
            if ( TRUE == gtk_file_filter_filter (image_list->priv->filter, &filter_info))
Stephan Arts's avatar
Stephan Arts committed
291
            {
292 293
                g_object_ref (G_OBJECT (file));

294 295 296 297 298 299 300
                image_list->priv->images = g_list_insert_sorted (image_list->priv->images, file, rstto_image_list_get_compare_func (image_list));

                image_list->priv->n_images++;

                g_signal_emit (G_OBJECT (image_list), rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_NEW_IMAGE], 0, file, NULL);
                /** TODO: update all iterators */
                while (iter)
Stephan Arts's avatar
Stephan Arts committed
301
                {
302 303 304 305 306
                    if (FALSE == RSTTO_IMAGE_LIST_ITER(iter->data)->priv->sticky)
                    {
                        rstto_image_list_iter_find_file (iter->data, file);
                    }
                    iter = g_slist_next (iter);
Stephan Arts's avatar
Stephan Arts committed
307
                }
308 309 310 311 312
                return TRUE;
            }
            else
            {
                return FALSE;
Stephan Arts's avatar
Stephan Arts committed
313 314
            }
        }
Stephan Arts's avatar
Stephan Arts committed
315
        return FALSE;
316
    }
317

Stephan Arts's avatar
Stephan Arts committed
318 319 320
    g_signal_emit (G_OBJECT (image_list), rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_NEW_IMAGE], 0, image_iter->data, NULL);

    return TRUE;
321 322 323 324 325 326 327 328
}

gint
rstto_image_list_get_n_images (RsttoImageList *image_list)
{
    return g_list_length (image_list->priv->images);
}

Stephan Arts's avatar
Stephan Arts committed
329 330 331 332 333 334 335 336
/**
 * rstto_image_list_get_iter:
 * @image_list:
 *
 * TODO: track iterators
 *
 * return iter;
 */
337 338 339
RsttoImageListIter *
rstto_image_list_get_iter (RsttoImageList *image_list)
{
Stephan Arts's avatar
Stephan Arts committed
340
    RsttoFile *file = NULL;
Stephan Arts's avatar
Stephan Arts committed
341
    RsttoImageListIter *iter = NULL;
342
    if (image_list->priv->images)
343
        file = image_list->priv->images->data;
344

345
    iter = rstto_image_list_iter_new (image_list, file);
346

Stephan Arts's avatar
Stephan Arts committed
347 348
    image_list->priv->iterators = g_slist_prepend (image_list->priv->iterators, iter);

349 350 351 352 353
    return iter;
}


void
Stephan Arts's avatar
Stephan Arts committed
354
rstto_image_list_remove_file (RsttoImageList *image_list, RsttoFile *file)
355
{
Stephan Arts's avatar
Stephan Arts committed
356
    GSList *iter = NULL;
Stephan Arts's avatar
Stephan Arts committed
357
    RsttoFile *afile = NULL;
Stephan Arts's avatar
Stephan Arts committed
358

359
    if (g_list_find(image_list->priv->images, file))
360
    {
Stephan Arts's avatar
Stephan Arts committed
361

Stephan Arts's avatar
Stephan Arts committed
362
        iter = image_list->priv->iterators;
Stephan Arts's avatar
Stephan Arts committed
363 364
        while (iter)
        {
Stephan Arts's avatar
Stephan Arts committed
365
            if (rstto_file_equal(rstto_image_list_iter_get_file (iter->data), file))
Stephan Arts's avatar
Stephan Arts committed
366
            {
367
                if (rstto_image_list_iter_get_position (iter->data) == rstto_image_list_get_n_images (image_list)-1)
368
                {
369
                    iter_previous (iter->data, FALSE);
370 371 372
                }
                else
                {
373
                    iter_next (iter->data, FALSE);
374
                }
375 376 377 378
                /* If the image is still the same, 
                 * it's a single item list,
                 * and we should force the image in this iter to NULL
                 */
Stephan Arts's avatar
Stephan Arts committed
379
                if (rstto_file_equal(rstto_image_list_iter_get_file (iter->data), file))
380
                {
381
                    ((RsttoImageListIter *)(iter->data))->priv->file = NULL;
382
                    g_signal_emit (G_OBJECT (iter->data), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED], 0, NULL);
383
                }
Stephan Arts's avatar
Stephan Arts committed
384 385 386
            }
            iter = g_slist_next (iter);
        }
387

388
        image_list->priv->images = g_list_remove (image_list->priv->images, file);
389 390 391
        iter = image_list->priv->iterators;
        while (iter)
        {
392 393
            afile = rstto_image_list_iter_get_file(iter->data);
            if (NULL != afile)
394
            {
Stephan Arts's avatar
Stephan Arts committed
395
                if (rstto_file_equal(afile, file))
396
                {
397
                    iter_next (iter->data, FALSE);
398
                }
399 400 401 402
            }
            iter = g_slist_next (iter);
        }

403 404
        g_signal_emit (G_OBJECT (image_list), rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_REMOVE_IMAGE], 0, file, NULL);
        g_object_unref(file);
405 406 407
    }
}

408
static void
409 410
rstto_image_list_remove_all (RsttoImageList *image_list)
{
Stephan Arts's avatar
Stephan Arts committed
411
    GSList *iter = NULL;
412 413 414
    g_list_foreach (image_list->priv->images, (GFunc)g_object_unref, NULL);
    g_list_free (image_list->priv->images);
    image_list->priv->images = NULL;
Stephan Arts's avatar
Stephan Arts committed
415

Stephan Arts's avatar
Stephan Arts committed
416
    iter = image_list->priv->iterators;
Stephan Arts's avatar
Stephan Arts committed
417 418
    while (iter)
    {
419
        iter_set_position (iter->data, -1, FALSE);
Stephan Arts's avatar
Stephan Arts committed
420 421 422
        iter = g_slist_next (iter);
    }
    g_signal_emit (G_OBJECT (image_list), rstto_image_list_signals[RSTTO_IMAGE_LIST_SIGNAL_REMOVE_ALL], 0, NULL);
423 424
}

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 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
gboolean
rstto_image_list_set_directory (
        RsttoImageList *image_list,
        GFile *dir,
        GError **error )
{
    /* Declare variables */
    GFileEnumerator *file_enumerator = NULL;
    GFileInfo *file_info;
    const gchar *filename;
    const gchar *content_type;
    GFile *child_file;

    /* Source code block */
    rstto_image_list_remove_all (image_list);

    /* Allow all images to be removed by providing NULL to dir */
    if ( NULL != dir )
    {
        file_enumerator = g_file_enumerate_children (dir, "standard::*", 0, NULL, NULL);

        if (NULL != file_enumerator)
        {
            for(file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL);
                NULL != file_info;
                file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL))
            {
                filename = g_file_info_get_name (file_info);
                content_type  = g_file_info_get_content_type (file_info);
                child_file = g_file_get_child (dir, filename);
                if (strncmp (content_type, "image/", 6) == 0)
                {
                    rstto_image_list_add_file (image_list, rstto_file_new (child_file), NULL);
                }
            }
            g_object_unref (file_enumerator);
            file_enumerator = NULL;
        }
    }

    rstto_image_list_monitor_dir ( image_list, dir );

    return TRUE;
}

static void
Stephan Arts's avatar
Stephan Arts committed
471 472 473 474 475 476 477 478 479 480 481 482
rstto_image_list_monitor_dir (
        RsttoImageList *image_list,
        GFile *dir )
{
    GFileMonitor *monitor = NULL;

    if ( NULL != image_list->priv->monitor )
    {
        g_object_unref (image_list->priv->monitor);
        image_list->priv->monitor = NULL;
    }

483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    /* Allow a monitor to be removed by providing NULL to dir */
    if ( NULL != dir )
    {
        monitor = g_file_monitor_directory (
                dir,
                G_FILE_MONITOR_NONE,
                NULL,
                NULL);

        g_signal_connect (
                G_OBJECT(monitor),
                "changed",
                G_CALLBACK (cb_file_monitor_changed),
                image_list);
    }
Stephan Arts's avatar
Stephan Arts committed
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524

    image_list->priv->monitor = monitor;
}

static void
cb_file_monitor_changed (
        GFileMonitor      *monitor,
        GFile             *file,
        GFile             *other_file,
        GFileMonitorEvent  event_type,
        gpointer           user_data )
{
    RsttoImageList *image_list = RSTTO_IMAGE_LIST (user_data);
    RsttoFile *r_file = rstto_file_new (file);

    g_return_if_fail ( monitor == image_list->priv->monitor);

    switch ( event_type )
    {
        case G_FILE_MONITOR_EVENT_DELETED:
            rstto_image_list_remove_file ( image_list, r_file );
            break;
        case G_FILE_MONITOR_EVENT_CREATED:
            rstto_image_list_add_file (image_list, r_file, NULL);
            break;
        case G_FILE_MONITOR_EVENT_MOVED:
            rstto_image_list_remove_file ( image_list, r_file );
525 526 527 528

            /* Remove our reference, reusing pointer */
            g_object_unref (r_file);

Stephan Arts's avatar
Stephan Arts committed
529 530 531 532 533 534 535 536 537 538 539 540 541
            r_file = rstto_file_new (other_file);
            rstto_image_list_add_file (image_list, r_file, NULL);
            break;
        default:
            break;
    }

    if ( NULL != r_file )
    {
        g_object_unref (r_file);
    }
}

542 543 544


GType
Stephan Arts's avatar
Stephan Arts committed
545
rstto_image_list_iter_get_type (void)
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
{
    static GType rstto_image_list_iter_type = 0;

    if (!rstto_image_list_iter_type)
    {
        static const GTypeInfo rstto_image_list_iter_info = 
        {
            sizeof (RsttoImageListIterClass),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) rstto_image_list_iter_class_init,
            (GClassFinalizeFunc) NULL,
            NULL,
            sizeof (RsttoImageListIter),
            0,
            (GInstanceInitFunc) rstto_image_list_iter_init,
            NULL
        };

        rstto_image_list_iter_type = g_type_register_static (G_TYPE_OBJECT, "RsttoImageListIter", &rstto_image_list_iter_info, 0);
    }
    return rstto_image_list_iter_type;
}

static void
rstto_image_list_iter_init (RsttoImageListIter *iter)
{
    iter->priv = g_new0 (RsttoImageListIterPriv, 1);
}

static void
rstto_image_list_iter_class_init(RsttoImageListIterClass *iter_class)
{
    GObjectClass *object_class = G_OBJECT_CLASS(iter_class);

    iter_parent_class = g_type_class_peek_parent(iter_class);

    object_class->dispose = rstto_image_list_iter_dispose;

585 586 587 588 589 590 591 592 593 594 595
    rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_PREPARE_CHANGE] = g_signal_new("prepare-change",
            G_TYPE_FROM_CLASS(iter_class),
            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
            0,
            NULL,
            NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE,
            0,
            NULL);

596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
    rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED] = g_signal_new("changed",
            G_TYPE_FROM_CLASS(iter_class),
            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
            0,
            NULL,
            NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE,
            0,
            NULL);

}

static void
rstto_image_list_iter_dispose (GObject *object)
{
    RsttoImageListIter *iter = RSTTO_IMAGE_LIST_ITER(object);
613
    if (iter->priv->file)
614
    {
615
        iter->priv->file = NULL;
616
    }
Stephan Arts's avatar
Stephan Arts committed
617 618 619 620 621 622

    if (iter->priv->image_list)
    {
        iter->priv->image_list->priv->iterators = g_slist_remove (iter->priv->image_list->priv->iterators, iter);
        iter->priv->image_list= NULL;
    }
623 624 625
}

static RsttoImageListIter *
Stephan Arts's avatar
Stephan Arts committed
626
rstto_image_list_iter_new (RsttoImageList *nav, RsttoFile *file)
627 628 629 630
{
    RsttoImageListIter *iter;

    iter = g_object_new(RSTTO_TYPE_IMAGE_LIST_ITER, NULL);
631
    iter->priv->file = file;
632 633 634 635 636 637
    iter->priv->image_list = nav;

    return iter;
}

gboolean
Stephan Arts's avatar
Stephan Arts committed
638
rstto_image_list_iter_find_file (RsttoImageListIter *iter, RsttoFile *file)
639
{
640
    gint pos = g_list_index (iter->priv->image_list->priv->images, file);
641 642
    if (pos > -1)
    {
643 644
        g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_PREPARE_CHANGE], 0, NULL);

645
        if (iter->priv->file)
646
        {
647
            iter->priv->file = NULL;
648
        }
649
        iter->priv->file = file;
650
        iter->priv->sticky = TRUE;
651 652 653 654 655 656 657 658 659 660 661

        g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED], 0, NULL);

        return TRUE;
    }
    return FALSE;
}

gint
rstto_image_list_iter_get_position (RsttoImageListIter *iter)
{
662
    if ( NULL == iter->priv->file )
663
    {
664
        return -1;
665
    }
666
    return g_list_index (iter->priv->image_list->priv->images, iter->priv->file);
667 668
}

Stephan Arts's avatar
Stephan Arts committed
669
RsttoFile *
670
rstto_image_list_iter_get_file (RsttoImageListIter *iter)
671
{
672
    return iter->priv->file;
673 674
}

675 676 677 678 679
static void
iter_set_position (
        RsttoImageListIter *iter,
        gint pos,
        gboolean sticky )
680
{
681 682
    g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_PREPARE_CHANGE], 0, NULL);

683
    if (iter->priv->file)
684
    {
685
        iter->priv->file = NULL;
686 687
    }

Stephan Arts's avatar
Stephan Arts committed
688
    if (pos >= 0)
Stephan Arts's avatar
Stephan Arts committed
689
    {
690
        iter->priv->file = g_list_nth_data (iter->priv->image_list->priv->images, pos); 
Stephan Arts's avatar
Stephan Arts committed
691
    }
692

693 694 695
    g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED], 0, NULL);
}

696 697 698 699 700 701 702 703 704 705
void
rstto_image_list_iter_set_position (RsttoImageListIter *iter, gint pos)
{
    iter_set_position ( iter, pos, TRUE );
}

static gboolean
iter_next (
        RsttoImageListIter *iter,
        gboolean sticky)
706
{
707
    GList *position = NULL;
708
    RsttoImageList *image_list = iter->priv->image_list;
709
    RsttoFile *file = iter->priv->file;
710
    gboolean ret_val = FALSE;
711 712 713

    g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_PREPARE_CHANGE], 0, NULL);

714
    if (iter->priv->file)
715
    {
716 717
        position = g_list_find (iter->priv->image_list->priv->images, iter->priv->file);
        iter->priv->file = NULL;
718 719
    }

720 721
    iter->priv->sticky = sticky;

722 723
    position = g_list_next (position);
    if (position)
724
    {
725
        iter->priv->file = position->data; 
726 727 728 729

        /* We could move forward, set ret_val to TRUE */
        ret_val = TRUE;
    }
730 731
    else
    {
Stephan Arts's avatar
Stephan Arts committed
732

733
        if (TRUE == image_list->priv->wrap_images)
734
        {
735
            position = g_list_first (iter->priv->image_list->priv->images);
736 737 738 739 740 741

            /* We could move forward, wrapped back to the start of the
             * list, set ret_val to TRUE
             */
            ret_val = TRUE;
        }
742 743 744
        else
            position = g_list_last (iter->priv->image_list->priv->images);

745
        if (position)
746
            iter->priv->file = position->data; 
747
        else
748
            iter->priv->file = NULL;
749 750
    }

751 752 753 754
    if (file != iter->priv->file)
    {
        g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED], 0, NULL);
    }
755 756

    return ret_val;
757 758
}

759
gboolean
760 761 762 763 764
rstto_image_list_iter_next (RsttoImageListIter *iter)
{
    return iter_next (iter, TRUE);
}

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
gboolean
rstto_image_list_iter_has_next (RsttoImageListIter *iter)
{
    RsttoImageList *image_list = iter->priv->image_list;

    if (image_list->priv->wrap_images)
    {
        return TRUE;
    }
    else
    {
        if (rstto_image_list_iter_get_position (iter) ==
            (rstto_image_list_get_n_images (image_list) -1))
        {
            return FALSE;
        }
        else
        {
            return TRUE;
        }
    }
}

788 789 790 791
static gboolean
iter_previous (
        RsttoImageListIter *iter,
        gboolean sticky)
792
{
793
    GList *position = NULL;
794
    RsttoImageList *image_list = iter->priv->image_list;
795
    RsttoFile *file = iter->priv->file;
796
    gboolean ret_val = FALSE;
797 798 799

    g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_PREPARE_CHANGE], 0, NULL);

800
    if (iter->priv->file)
801
    {
802 803
        position = g_list_find (iter->priv->image_list->priv->images, iter->priv->file);
        iter->priv->file = NULL;
804 805
    }

806 807
    iter->priv->sticky = sticky;

808 809
    position = g_list_previous (position);
    if (position)
810
    {
811
        iter->priv->file = position->data; 
812
    }
813 814
    else
    {
815 816
        if (TRUE == image_list->priv->wrap_images)
        {
817
            position = g_list_last (iter->priv->image_list->priv->images);
818
        }
819
        else
820
        {
821
            position = g_list_first (iter->priv->image_list->priv->images);
822
        }
823

824
        if (position)
825
        {
826
            iter->priv->file = position->data; 
827
        }
828
        else
829
        {
830
            iter->priv->file = NULL;
831
        }
832
    }
833

834 835 836 837
    if (file != iter->priv->file)
    {
        g_signal_emit (G_OBJECT (iter), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED], 0, NULL);
    }
838 839

    return ret_val;
840 841 842 843 844 845

}
gboolean
rstto_image_list_iter_previous (RsttoImageListIter *iter)
{
    return iter_previous (iter, TRUE);
846 847
}

848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871

gboolean
rstto_image_list_iter_has_previous (RsttoImageListIter *iter)
{
    RsttoImageList *image_list = iter->priv->image_list;

    if (image_list->priv->wrap_images)
    {
        return TRUE;
    }
    else
    {
        if (rstto_image_list_iter_get_position (iter) == 0)
        {
            return FALSE;
        }
        else
        {
            return TRUE;
        }
    }
}


872 873 874
RsttoImageListIter *
rstto_image_list_iter_clone (RsttoImageListIter *iter)
{
875
    RsttoImageListIter *new_iter = rstto_image_list_iter_new (iter->priv->image_list, iter->priv->file);
Stephan Arts's avatar
Stephan Arts committed
876
    rstto_image_list_iter_set_position (new_iter, rstto_image_list_iter_get_position(iter));
877 878 879

    return new_iter;
}
880 881 882 883

GCompareFunc
rstto_image_list_get_compare_func (RsttoImageList *image_list)
{
Stephan Arts's avatar
Stephan Arts committed
884 885 886
    return (GCompareFunc)image_list->priv->cb_rstto_image_list_compare_func;
}

887 888 889
void
rstto_image_list_set_compare_func (RsttoImageList *image_list, GCompareFunc func)
{
890
    GSList *iter = NULL;
891
    image_list->priv->cb_rstto_image_list_compare_func = func;
892
    image_list->priv->images = g_list_sort (image_list->priv->images,  func);
893

894 895 896 897
    for (iter = image_list->priv->iterators; iter != NULL; iter = g_slist_next (iter))
    {
        g_signal_emit (G_OBJECT (iter->data), rstto_image_list_iter_signals[RSTTO_IMAGE_LIST_ITER_SIGNAL_CHANGED], 0, NULL);
    }
898 899
}

Stephan Arts's avatar
Stephan Arts committed
900 901 902 903
/***********************/
/*  Compare Functions  */
/***********************/

904 905 906
void
rstto_image_list_set_sort_by_name (RsttoImageList *image_list)
{
907
    rstto_image_list_set_compare_func (image_list, (GCompareFunc)cb_rstto_image_list_image_name_compare_func);
908 909 910 911 912
}

void
rstto_image_list_set_sort_by_date (RsttoImageList *image_list)
{
913
    rstto_image_list_set_compare_func (image_list, (GCompareFunc)cb_rstto_image_list_exif_date_compare_func);
914 915
}

Stephan Arts's avatar
Stephan Arts committed
916 917 918 919 920 921 922 923 924
/**
 * cb_rstto_image_list_image_name_compare_func:
 * @a:
 * @b:
 *
 *
 * Return value: (see strcmp)
 */
static gint
Stephan Arts's avatar
Stephan Arts committed
925
cb_rstto_image_list_image_name_compare_func (RsttoFile *a, RsttoFile *b)
Stephan Arts's avatar
Stephan Arts committed
926
{
Stephan Arts's avatar
Stephan Arts committed
927 928
    const gchar *a_base = rstto_file_get_display_name (a);
    const gchar *b_base = rstto_file_get_display_name (b);
929 930 931 932 933
    guint  ac;
    guint  bc;
    const gchar *ap = a_base;
    const gchar *bp = b_base;

Stephan Arts's avatar
Stephan Arts committed
934
    gint result = 0;
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960
    guint a_num = 0;
    guint b_num = 0;

    /* try simple (fast) ASCII comparison first */
    for (;; ++ap, ++bp)
    {
        /* check if the characters differ or we have a non-ASCII char
         */
        ac = *((const guchar *) ap);
        bc = *((const guchar *) bp);
        if (ac != bc || ac == 0 || ac > 127)
            break;
    }

    /* fallback to Unicode comparison */
    if (G_UNLIKELY (ac > 127 || bc > 127))
    {
        for (;; ap = g_utf8_next_char (ap), bp = g_utf8_next_char (bp))
        {
            /* check if characters differ or end of string */
            ac = g_utf8_get_char (ap);
            bc = g_utf8_get_char (bp);
            if (ac != bc || ac == 0)
                break;
        }
    }
Stephan Arts's avatar
Stephan Arts committed
961

962
    /* If both strings are equal, we're done */
Stephan Arts's avatar
Stephan Arts committed
963 964 965 966 967
    if (ac == bc)
    {
        return 0;
    }
    else
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
    {
        if (G_UNLIKELY (g_ascii_isdigit (ac) || g_ascii_isdigit (bc)))
        {
            /* if both strings differ in a digit, we use a smarter comparison
             * to get sorting 'file1', 'file5', 'file10' done the right way.
             */
            if (g_ascii_isdigit (ac) && g_ascii_isdigit (bc))
            {
                a_num = strtoul (ap, NULL, 10); 
                b_num = strtoul (bp, NULL, 10); 

                if (a_num < b_num)
                    result = -1;
                if (a_num > b_num)
                    result = 1;
            }
984 985 986 987 988 989 990 991 992 993 994 995 996 997

            if (ap > a_base &&
                bp > b_base &&
                g_ascii_isdigit (*(ap -1)) &&
                g_ascii_isdigit (*(bp -1)) )
            {
                a_num = strtoul (ap-1, NULL, 10); 
                b_num = strtoul (bp-1, NULL, 10); 

                if (a_num < b_num)
                    result = -1;
                if (a_num > b_num)
                    result = 1;
            }
998 999
        }
    }
Stephan Arts's avatar
Stephan Arts committed
1000

1001 1002 1003 1004
    if (result == 0)
    {
        if (ac > bc)
            result = 1;
Stephan Arts's avatar
Stephan Arts committed
1005
        if (ac < bc)
1006 1007 1008 1009
            result = -1;
    }


Stephan Arts's avatar
Stephan Arts committed
1010 1011 1012 1013 1014 1015 1016 1017
    return result;
}

/**
 * cb_rstto_image_list_exif_date_compare_func:
 * @a:
 * @b:
 *
1018
 * TODO: Use EXIF data if available, not the last-modification-time.
Stephan Arts's avatar
Stephan Arts committed
1019 1020 1021 1022
 *
 * Return value: (see strcmp)
 */
static gint
Stephan Arts's avatar
Stephan Arts committed
1023
cb_rstto_image_list_exif_date_compare_func (RsttoFile *a, RsttoFile *b)
Stephan Arts's avatar
Stephan Arts committed
1024
{
1025 1026 1027 1028 1029 1030 1031 1032
    guint64 a_t = rstto_file_get_modified_time (a);
    guint64 b_t = rstto_file_get_modified_time (b);

    if (a_t < b_t)
    {
        return -1;
    }
    return 1;
1033
}
1034 1035 1036 1037 1038 1039 1040

gboolean
rstto_image_list_iter_get_sticky (
        RsttoImageListIter *iter)
{
    return iter->priv->sticky;
}
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060

static void
cb_rstto_wrap_images_changed (
        GObject *settings,
        GParamSpec *pspec,
        gpointer user_data)
{
    GValue val_wrap_images = { 0, };

    RsttoImageList *image_list = RSTTO_IMAGE_LIST (user_data);

    g_value_init (&val_wrap_images, G_TYPE_BOOLEAN);

    g_object_get_property (
            settings,
            "wrap-images",
            &val_wrap_images);

    image_list->priv->wrap_images = g_value_get_boolean (&val_wrap_images);
}