436 lines
10 KiB
C
436 lines
10 KiB
C
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <lardon3d/image_view.h>
|
|
|
|
struct Lardon3DImageView {
|
|
const Lardon3DImageCatalog *catalog;
|
|
size_t *indices;
|
|
size_t count;
|
|
uint64_t total_size;
|
|
Lardon3DImageSort sort;
|
|
char filter[LARDON3D_IMAGE_FILTER_CAPACITY];
|
|
size_t selection;
|
|
size_t offset;
|
|
};
|
|
|
|
static unsigned char
|
|
ascii_lower(unsigned char character)
|
|
{
|
|
return character >= 'A' && character <= 'Z'
|
|
? (unsigned char)(character + ('a' - 'A'))
|
|
: character;
|
|
}
|
|
|
|
bool
|
|
lardon3d_image_view_ascii_contains(
|
|
const char *text,
|
|
const char *filter
|
|
)
|
|
{
|
|
if (!text || !filter) {
|
|
return false;
|
|
}
|
|
if (!filter[0]) {
|
|
return true;
|
|
}
|
|
for (const unsigned char *start = (const unsigned char *)text;
|
|
*start;
|
|
++start) {
|
|
const unsigned char *left = start;
|
|
const unsigned char *right = (const unsigned char *)filter;
|
|
while (*left && *right && ascii_lower(*left) == ascii_lower(*right)) {
|
|
++left;
|
|
++right;
|
|
}
|
|
if (!*right) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int
|
|
compare_indices(
|
|
const Lardon3DImageView *view,
|
|
size_t left_index,
|
|
size_t right_index
|
|
)
|
|
{
|
|
const Lardon3DImageEntry *left = lardon3d_image_catalog_get(
|
|
view->catalog,
|
|
left_index
|
|
);
|
|
const Lardon3DImageEntry *right = lardon3d_image_catalog_get(
|
|
view->catalog,
|
|
right_index
|
|
);
|
|
if (!left || !right) {
|
|
return left ? -1 : right ? 1 : 0;
|
|
}
|
|
|
|
int name_comparison = strcmp(left->filename, right->filename);
|
|
switch (view->sort) {
|
|
case LARDON3D_IMAGE_SORT_NAME_ASC:
|
|
return name_comparison;
|
|
case LARDON3D_IMAGE_SORT_NAME_DESC:
|
|
return name_comparison > 0 ? -1 : name_comparison < 0 ? 1 : 0;
|
|
case LARDON3D_IMAGE_SORT_SIZE_ASC:
|
|
case LARDON3D_IMAGE_SORT_SIZE_DESC:
|
|
if (left->size_bytes != right->size_bytes) {
|
|
bool left_first = view->sort == LARDON3D_IMAGE_SORT_SIZE_ASC
|
|
? left->size_bytes < right->size_bytes
|
|
: left->size_bytes > right->size_bytes;
|
|
return left_first ? -1 : 1;
|
|
}
|
|
return name_comparison;
|
|
case LARDON3D_IMAGE_SORT_IMPORT_ORDER:
|
|
default:
|
|
return left_index < right_index ? -1 : left_index > right_index ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
merge_sort_range(
|
|
Lardon3DImageView *view,
|
|
size_t *indices,
|
|
size_t *temporary,
|
|
size_t begin,
|
|
size_t end
|
|
)
|
|
{
|
|
if (end - begin < 2) {
|
|
return;
|
|
}
|
|
size_t middle = begin + (end - begin) / 2;
|
|
merge_sort_range(view, indices, temporary, begin, middle);
|
|
merge_sort_range(view, indices, temporary, middle, end);
|
|
|
|
size_t left = begin;
|
|
size_t right = middle;
|
|
size_t output = begin;
|
|
while (left < middle || right < end) {
|
|
if (right >= end
|
|
|| (left < middle
|
|
&& compare_indices(view, indices[left], indices[right]) <= 0)) {
|
|
temporary[output++] = indices[left++];
|
|
} else {
|
|
temporary[output++] = indices[right++];
|
|
}
|
|
}
|
|
(void)memcpy(
|
|
indices + begin,
|
|
temporary + begin,
|
|
(end - begin) * sizeof(*indices)
|
|
);
|
|
}
|
|
|
|
static bool
|
|
selected_catalog_index(
|
|
const Lardon3DImageView *view,
|
|
size_t *catalog_index
|
|
)
|
|
{
|
|
if (!view || view->selection >= view->count) {
|
|
return false;
|
|
}
|
|
*catalog_index = view->indices[view->selection];
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
build_view(
|
|
Lardon3DImageView *view,
|
|
const Lardon3DImageCatalog *catalog,
|
|
Lardon3DImageSort sort,
|
|
const char *filter
|
|
)
|
|
{
|
|
char filter_copy[LARDON3D_IMAGE_FILTER_CAPACITY];
|
|
(void)snprintf(filter_copy, sizeof(filter_copy), "%s", filter);
|
|
size_t preserved_index = 0;
|
|
bool preserve = selected_catalog_index(view, &preserved_index);
|
|
size_t catalog_count = lardon3d_image_catalog_count(catalog);
|
|
if (catalog_count > SIZE_MAX / sizeof(*view->indices)) {
|
|
return false;
|
|
}
|
|
size_t *indices = catalog_count > 0
|
|
? malloc(catalog_count * sizeof(*indices))
|
|
: NULL;
|
|
if (catalog_count > 0 && !indices) {
|
|
return false;
|
|
}
|
|
|
|
size_t count = 0;
|
|
uint64_t total_size = 0;
|
|
for (size_t index = 0; index < catalog_count; ++index) {
|
|
const Lardon3DImageEntry *entry = lardon3d_image_catalog_get(
|
|
catalog,
|
|
index
|
|
);
|
|
if (entry
|
|
&& lardon3d_image_view_ascii_contains(
|
|
entry->filename,
|
|
filter_copy
|
|
)) {
|
|
indices[count++] = index;
|
|
total_size += entry->size_bytes;
|
|
}
|
|
}
|
|
|
|
const Lardon3DImageCatalog *old_catalog = view->catalog;
|
|
Lardon3DImageSort old_sort = view->sort;
|
|
view->catalog = catalog;
|
|
view->sort = sort;
|
|
if (sort != LARDON3D_IMAGE_SORT_IMPORT_ORDER && count > 1) {
|
|
size_t *temporary = malloc(count * sizeof(*temporary));
|
|
if (!temporary) {
|
|
view->catalog = old_catalog;
|
|
view->sort = old_sort;
|
|
free(indices);
|
|
return false;
|
|
}
|
|
merge_sort_range(view, indices, temporary, 0, count);
|
|
free(temporary);
|
|
}
|
|
|
|
free(view->indices);
|
|
view->indices = indices;
|
|
view->count = count;
|
|
view->total_size = total_size;
|
|
view->selection = 0;
|
|
view->offset = 0;
|
|
(void)snprintf(view->filter, sizeof(view->filter), "%s", filter_copy);
|
|
if (preserve) {
|
|
for (size_t index = 0; index < count; ++index) {
|
|
if (indices[index] == preserved_index) {
|
|
view->selection = index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Lardon3DImageView *
|
|
lardon3d_image_view_create(const Lardon3DImageCatalog *catalog)
|
|
{
|
|
Lardon3DImageView *view = calloc(1, sizeof(*view));
|
|
if (!view) {
|
|
return NULL;
|
|
}
|
|
view->catalog = catalog;
|
|
view->sort = LARDON3D_IMAGE_SORT_IMPORT_ORDER;
|
|
if (!build_view(view, catalog, view->sort, "")) {
|
|
free(view);
|
|
return NULL;
|
|
}
|
|
return view;
|
|
}
|
|
|
|
void
|
|
lardon3d_image_view_destroy(Lardon3DImageView *view)
|
|
{
|
|
if (!view) {
|
|
return;
|
|
}
|
|
free(view->indices);
|
|
free(view);
|
|
}
|
|
|
|
bool
|
|
lardon3d_image_view_set_catalog(
|
|
Lardon3DImageView *view,
|
|
const Lardon3DImageCatalog *catalog
|
|
)
|
|
{
|
|
return view && build_view(view, catalog, view->sort, view->filter);
|
|
}
|
|
|
|
bool
|
|
lardon3d_image_view_set_sort(
|
|
Lardon3DImageView *view,
|
|
Lardon3DImageSort sort
|
|
)
|
|
{
|
|
if (!view || sort < LARDON3D_IMAGE_SORT_IMPORT_ORDER
|
|
|| sort > LARDON3D_IMAGE_SORT_SIZE_DESC) {
|
|
return false;
|
|
}
|
|
return build_view(view, view->catalog, sort, view->filter);
|
|
}
|
|
|
|
bool
|
|
lardon3d_image_view_set_filter(
|
|
Lardon3DImageView *view,
|
|
const char *filter,
|
|
char *error_message,
|
|
size_t error_message_size
|
|
)
|
|
{
|
|
if (error_message && error_message_size > 0) {
|
|
error_message[0] = '\0';
|
|
}
|
|
if (!view || !filter) {
|
|
if (error_message && error_message_size > 0) {
|
|
(void)snprintf(
|
|
error_message,
|
|
error_message_size,
|
|
"Erreur : filtre invalide."
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
if (strnlen(filter, LARDON3D_IMAGE_FILTER_CAPACITY)
|
|
>= LARDON3D_IMAGE_FILTER_CAPACITY) {
|
|
if (error_message && error_message_size > 0) {
|
|
(void)snprintf(
|
|
error_message,
|
|
error_message_size,
|
|
"Erreur : filtre trop long."
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
if (!build_view(view, view->catalog, view->sort, filter)) {
|
|
if (error_message && error_message_size > 0) {
|
|
(void)snprintf(
|
|
error_message,
|
|
error_message_size,
|
|
"Erreur : mémoire insuffisante pour la vue."
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_image_view_rebuild(Lardon3DImageView *view)
|
|
{
|
|
return view && build_view(view, view->catalog, view->sort, view->filter);
|
|
}
|
|
|
|
size_t
|
|
lardon3d_image_view_count(const Lardon3DImageView *view)
|
|
{
|
|
return view ? view->count : 0;
|
|
}
|
|
|
|
uint64_t
|
|
lardon3d_image_view_total_size(const Lardon3DImageView *view)
|
|
{
|
|
return view ? view->total_size : 0;
|
|
}
|
|
|
|
const Lardon3DImageEntry *
|
|
lardon3d_image_view_get(
|
|
const Lardon3DImageView *view,
|
|
size_t visible_index
|
|
)
|
|
{
|
|
return view && visible_index < view->count
|
|
? lardon3d_image_catalog_get(view->catalog, view->indices[visible_index])
|
|
: NULL;
|
|
}
|
|
|
|
bool
|
|
lardon3d_image_view_catalog_index(
|
|
const Lardon3DImageView *view,
|
|
size_t visible_index,
|
|
size_t *catalog_index
|
|
)
|
|
{
|
|
if (!view || !catalog_index || visible_index >= view->count) {
|
|
return false;
|
|
}
|
|
*catalog_index = view->indices[visible_index];
|
|
return true;
|
|
}
|
|
|
|
Lardon3DImageSort
|
|
lardon3d_image_view_sort(const Lardon3DImageView *view)
|
|
{
|
|
return view ? view->sort : LARDON3D_IMAGE_SORT_IMPORT_ORDER;
|
|
}
|
|
|
|
const char *
|
|
lardon3d_image_view_filter(const Lardon3DImageView *view)
|
|
{
|
|
return view ? view->filter : "";
|
|
}
|
|
|
|
const char *
|
|
lardon3d_image_view_sort_name(Lardon3DImageSort sort)
|
|
{
|
|
switch (sort) {
|
|
case LARDON3D_IMAGE_SORT_NAME_ASC:
|
|
return "Nom croissant";
|
|
case LARDON3D_IMAGE_SORT_NAME_DESC:
|
|
return "Nom décroissant";
|
|
case LARDON3D_IMAGE_SORT_SIZE_ASC:
|
|
return "Taille croissante";
|
|
case LARDON3D_IMAGE_SORT_SIZE_DESC:
|
|
return "Taille décroissante";
|
|
case LARDON3D_IMAGE_SORT_IMPORT_ORDER:
|
|
default:
|
|
return "Ordre d'import";
|
|
}
|
|
}
|
|
|
|
size_t
|
|
lardon3d_image_view_selection(const Lardon3DImageView *view)
|
|
{
|
|
return view ? view->selection : 0;
|
|
}
|
|
|
|
size_t
|
|
lardon3d_image_view_offset(const Lardon3DImageView *view)
|
|
{
|
|
return view ? view->offset : 0;
|
|
}
|
|
|
|
void
|
|
lardon3d_image_view_normalize(
|
|
Lardon3DImageView *view,
|
|
size_t page_size
|
|
)
|
|
{
|
|
if (!view) {
|
|
return;
|
|
}
|
|
if (view->count == 0) {
|
|
view->selection = 0;
|
|
view->offset = 0;
|
|
return;
|
|
}
|
|
if (view->selection >= view->count) {
|
|
view->selection = view->count - 1;
|
|
}
|
|
if (view->offset > view->selection) {
|
|
view->offset = view->selection;
|
|
}
|
|
if (page_size == 0) {
|
|
page_size = 1;
|
|
}
|
|
if (view->selection - view->offset >= page_size) {
|
|
view->offset = view->selection - page_size + 1;
|
|
}
|
|
}
|
|
|
|
void
|
|
lardon3d_image_view_select(
|
|
Lardon3DImageView *view,
|
|
size_t visible_index,
|
|
size_t page_size
|
|
)
|
|
{
|
|
if (!view) {
|
|
return;
|
|
}
|
|
view->selection = visible_index;
|
|
lardon3d_image_view_normalize(view, page_size);
|
|
}
|