feat: add image catalog sorting and filtering
This commit is contained in:
parent
118172ad0e
commit
2fe594cc51
10 changed files with 1113 additions and 64 deletions
|
|
@ -39,3 +39,7 @@ opération, la touche `C` demande son annulation.
|
|||
L'écran Import présente un catalogue en mémoire vérifié par rapport au manifeste
|
||||
et aux fichiers de `images/originals`. Les flèches, `j`/`k`, PageUp, PageDown,
|
||||
Home et End naviguent dans la liste ; `R` recharge le catalogue.
|
||||
La touche `S` fait défiler les tris par ordre d'import, nom ou taille, dans les
|
||||
deux directions. `/` filtre les noms d'images sans distinction de casse ASCII et
|
||||
`X` efface le filtre. Le tri et le filtre restent entièrement en mémoire et ne
|
||||
modifient ni le manifeste ni les images.
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct Lardon3DImageCatalog Lardon3DImageCatalog;
|
||||
typedef struct Lardon3DImageView Lardon3DImageView;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_SCREEN_HOME = 0,
|
||||
|
|
@ -23,8 +22,7 @@ typedef struct {
|
|||
char project_path[PATH_MAX];
|
||||
char status_message[256];
|
||||
Lardon3DImageCatalog *image_catalog;
|
||||
size_t image_selection;
|
||||
size_t image_offset;
|
||||
Lardon3DImageView *image_view;
|
||||
} Lardon3DAppState;
|
||||
|
||||
void lardon3d_app_state_init(Lardon3DAppState *state);
|
||||
|
|
|
|||
71
include/lardon3d/image_view.h
Normal file
71
include/lardon3d/image_view.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef LARDON3D_IMAGE_VIEW_H
|
||||
#define LARDON3D_IMAGE_VIEW_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lardon3d/image_catalog.h>
|
||||
|
||||
enum {
|
||||
LARDON3D_IMAGE_FILTER_CAPACITY = 128,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_IMAGE_SORT_IMPORT_ORDER = 0,
|
||||
LARDON3D_IMAGE_SORT_NAME_ASC,
|
||||
LARDON3D_IMAGE_SORT_NAME_DESC,
|
||||
LARDON3D_IMAGE_SORT_SIZE_ASC,
|
||||
LARDON3D_IMAGE_SORT_SIZE_DESC
|
||||
} Lardon3DImageSort;
|
||||
|
||||
Lardon3DImageView *lardon3d_image_view_create(
|
||||
const Lardon3DImageCatalog *catalog
|
||||
);
|
||||
void lardon3d_image_view_destroy(Lardon3DImageView *view);
|
||||
bool lardon3d_image_view_set_catalog(
|
||||
Lardon3DImageView *view,
|
||||
const Lardon3DImageCatalog *catalog
|
||||
);
|
||||
bool lardon3d_image_view_set_sort(
|
||||
Lardon3DImageView *view,
|
||||
Lardon3DImageSort sort
|
||||
);
|
||||
bool lardon3d_image_view_set_filter(
|
||||
Lardon3DImageView *view,
|
||||
const char *filter,
|
||||
char *error_message,
|
||||
size_t error_message_size
|
||||
);
|
||||
bool lardon3d_image_view_rebuild(Lardon3DImageView *view);
|
||||
size_t lardon3d_image_view_count(const Lardon3DImageView *view);
|
||||
uint64_t lardon3d_image_view_total_size(const Lardon3DImageView *view);
|
||||
const Lardon3DImageEntry *lardon3d_image_view_get(
|
||||
const Lardon3DImageView *view,
|
||||
size_t visible_index
|
||||
);
|
||||
bool lardon3d_image_view_catalog_index(
|
||||
const Lardon3DImageView *view,
|
||||
size_t visible_index,
|
||||
size_t *catalog_index
|
||||
);
|
||||
Lardon3DImageSort lardon3d_image_view_sort(const Lardon3DImageView *view);
|
||||
const char *lardon3d_image_view_filter(const Lardon3DImageView *view);
|
||||
const char *lardon3d_image_view_sort_name(Lardon3DImageSort sort);
|
||||
size_t lardon3d_image_view_selection(const Lardon3DImageView *view);
|
||||
size_t lardon3d_image_view_offset(const Lardon3DImageView *view);
|
||||
void lardon3d_image_view_select(
|
||||
Lardon3DImageView *view,
|
||||
size_t visible_index,
|
||||
size_t page_size
|
||||
);
|
||||
void lardon3d_image_view_normalize(
|
||||
Lardon3DImageView *view,
|
||||
size_t page_size
|
||||
);
|
||||
bool lardon3d_image_view_ascii_contains(
|
||||
const char *text,
|
||||
const char *filter
|
||||
);
|
||||
|
||||
#endif
|
||||
14
meson.build
14
meson.build
|
|
@ -32,6 +32,7 @@ executable(
|
|||
'src/import.c',
|
||||
'src/import_task.c',
|
||||
'src/image_catalog.c',
|
||||
'src/image_view.c',
|
||||
'src/project.c',
|
||||
],
|
||||
include_directories: include_directories('include'),
|
||||
|
|
@ -75,3 +76,16 @@ image_catalog_test = executable(
|
|||
)
|
||||
|
||||
test('image-catalog', image_catalog_test, timeout: 30)
|
||||
|
||||
image_view_test = executable(
|
||||
'test-image-view',
|
||||
sources: [
|
||||
'tests/test_image_view.c',
|
||||
'src/app_state.c',
|
||||
'src/image_catalog.c',
|
||||
'src/image_view.c',
|
||||
],
|
||||
include_directories: include_directories('include'),
|
||||
)
|
||||
|
||||
test('image-view', image_view_test, timeout: 30)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <lardon3d/app.h>
|
||||
#include <lardon3d/app_state.h>
|
||||
#include <lardon3d/image_catalog.h>
|
||||
#include <lardon3d/image_view.h>
|
||||
#include <lardon3d/tui.h>
|
||||
|
||||
int
|
||||
|
|
@ -21,6 +22,7 @@ lardon3d_app_run(void)
|
|||
}
|
||||
|
||||
bool success = lardon3d_tui_run(&state);
|
||||
lardon3d_image_view_destroy(state.image_view);
|
||||
lardon3d_image_catalog_destroy(state.image_catalog);
|
||||
lardon3d_tui_shutdown();
|
||||
|
||||
|
|
|
|||
436
src/image_view.c
Normal file
436
src/image_view.c
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
#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);
|
||||
}
|
||||
60
src/layout.c
60
src/layout.c
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <lardon3d/layout.h>
|
||||
#include <lardon3d/image_catalog.h>
|
||||
#include <lardon3d/image_view.h>
|
||||
|
||||
enum {
|
||||
MINIMUM_ROWS = 20,
|
||||
|
|
@ -71,7 +72,7 @@ screen_texts(
|
|||
case LARDON3D_SCREEN_IMPORT:
|
||||
*title = "Import";
|
||||
*content = "Import des images";
|
||||
*footer = "I Importer R Recharger ↑/↓ Naviguer ESC Accueil Q Quit";
|
||||
*footer = "I Importer R Recharger S Tri / Filtre X Effacer ↑/↓ ESC Q";
|
||||
break;
|
||||
case LARDON3D_SCREEN_VIEWER:
|
||||
*title = "Viewer";
|
||||
|
|
@ -143,34 +144,67 @@ draw_catalog(
|
|||
draw_text(7, 4, columns - 6, "Aucun projet chargé.");
|
||||
return;
|
||||
}
|
||||
size_t count = lardon3d_image_catalog_count(state->image_catalog);
|
||||
size_t count = lardon3d_image_view_count(state->image_view);
|
||||
size_t total = lardon3d_image_catalog_count(state->image_catalog);
|
||||
char line[512];
|
||||
(void)snprintf(
|
||||
line,
|
||||
sizeof(line),
|
||||
"Images importées : %zu",
|
||||
count
|
||||
"Images visibles : %zu / %zu",
|
||||
count,
|
||||
total
|
||||
);
|
||||
draw_text(6, 4, columns - 6, line);
|
||||
char size_text[64];
|
||||
lardon3d_image_catalog_format_size(
|
||||
lardon3d_image_catalog_total_size(state->image_catalog),
|
||||
lardon3d_image_view_total_size(state->image_view),
|
||||
size_text,
|
||||
sizeof(size_text)
|
||||
);
|
||||
(void)snprintf(line, sizeof(line), "Taille totale : %s", size_text);
|
||||
(void)snprintf(
|
||||
line,
|
||||
sizeof(line),
|
||||
"Taille totale visible : %s",
|
||||
size_text
|
||||
);
|
||||
draw_text(7, 4, columns - 6, line);
|
||||
(void)snprintf(
|
||||
line,
|
||||
sizeof(line),
|
||||
"Tri : %s",
|
||||
lardon3d_image_view_sort_name(
|
||||
lardon3d_image_view_sort(state->image_view)
|
||||
)
|
||||
);
|
||||
draw_text(8, 4, columns - 6, line);
|
||||
const char *filter = lardon3d_image_view_filter(state->image_view);
|
||||
(void)snprintf(
|
||||
line,
|
||||
sizeof(line),
|
||||
"Filtre : %s",
|
||||
filter[0] ? filter : "Aucun"
|
||||
);
|
||||
draw_text(9, 4, columns - 6, line);
|
||||
if (count == 0) {
|
||||
draw_text(9, 4, columns - 6, "Aucune image importée.");
|
||||
draw_text(
|
||||
11,
|
||||
4,
|
||||
columns - 6,
|
||||
filter[0]
|
||||
? "Aucune image ne correspond au filtre."
|
||||
: "Aucune image importée."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
int journal_row = rows - 7;
|
||||
size_t visible = journal_row > 9 ? (size_t)(journal_row - 9) : 0;
|
||||
size_t visible = journal_row > 11 ? (size_t)(journal_row - 11) : 0;
|
||||
size_t offset = lardon3d_image_view_offset(state->image_view);
|
||||
size_t selection = lardon3d_image_view_selection(state->image_view);
|
||||
for (size_t row = 0; row < visible; ++row) {
|
||||
size_t index = state->image_offset + row;
|
||||
const Lardon3DImageEntry *entry = lardon3d_image_catalog_get(
|
||||
state->image_catalog,
|
||||
size_t index = offset + row;
|
||||
const Lardon3DImageEntry *entry = lardon3d_image_view_get(
|
||||
state->image_view,
|
||||
index
|
||||
);
|
||||
if (!entry) {
|
||||
|
|
@ -185,11 +219,11 @@ draw_catalog(
|
|||
line,
|
||||
sizeof(line),
|
||||
"%c %s %s",
|
||||
index == state->image_selection ? '>' : ' ',
|
||||
index == selection ? '>' : ' ',
|
||||
entry->filename,
|
||||
size_text
|
||||
);
|
||||
draw_text(9 + (int)row, 4, columns - 6, line);
|
||||
draw_text(11 + (int)row, 4, columns - 6, line);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <lardon3d/project.h>
|
||||
#include <lardon3d/image_catalog.h>
|
||||
#include <lardon3d/image_view.h>
|
||||
|
||||
enum {
|
||||
MAX_CREATED_DIRECTORIES = 16,
|
||||
|
|
@ -36,10 +37,10 @@ set_status(Lardon3DAppState *state, const char *message)
|
|||
static void
|
||||
clear_catalog(Lardon3DAppState *state)
|
||||
{
|
||||
lardon3d_image_view_destroy(state->image_view);
|
||||
state->image_view = NULL;
|
||||
lardon3d_image_catalog_destroy(state->image_catalog);
|
||||
state->image_catalog = NULL;
|
||||
state->image_selection = 0;
|
||||
state->image_offset = 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
|
|||
263
src/tui.c
263
src/tui.c
|
|
@ -3,9 +3,11 @@
|
|||
#include <ncurses.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <lardon3d/import_task.h>
|
||||
#include <lardon3d/image_catalog.h>
|
||||
#include <lardon3d/image_view.h>
|
||||
#include <lardon3d/layout.h>
|
||||
#include <lardon3d/project.h>
|
||||
#include <lardon3d/tui.h>
|
||||
|
|
@ -20,6 +22,7 @@ typedef enum {
|
|||
INPUT_PROJECT_CREATE,
|
||||
INPUT_PROJECT_OPEN,
|
||||
INPUT_IMPORT_DIRECTORY,
|
||||
INPUT_IMAGE_FILTER,
|
||||
} InputMode;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -35,58 +38,108 @@ catalog_page_size(void)
|
|||
int columns;
|
||||
getmaxyx(stdscr, rows, columns);
|
||||
(void)columns;
|
||||
return rows > 16 ? (size_t)(rows - 16) : 1;
|
||||
return rows > 18 ? (size_t)(rows - 18) : 1;
|
||||
}
|
||||
|
||||
static void
|
||||
normalize_catalog_navigation(Lardon3DAppState *state)
|
||||
normalize_view_navigation(Lardon3DAppState *state)
|
||||
{
|
||||
size_t count = lardon3d_image_catalog_count(state->image_catalog);
|
||||
if (count == 0) {
|
||||
state->image_selection = 0;
|
||||
state->image_offset = 0;
|
||||
return;
|
||||
}
|
||||
if (state->image_selection >= count) {
|
||||
state->image_selection = count - 1;
|
||||
}
|
||||
if (state->image_offset > state->image_selection) {
|
||||
state->image_offset = state->image_selection;
|
||||
}
|
||||
size_t page_size = catalog_page_size();
|
||||
if (state->image_selection - state->image_offset >= page_size) {
|
||||
state->image_offset = state->image_selection - page_size + 1;
|
||||
}
|
||||
lardon3d_image_view_normalize(state->image_view, catalog_page_size());
|
||||
}
|
||||
|
||||
static bool
|
||||
reload_catalog(Lardon3DAppState *state, bool announce_success)
|
||||
{
|
||||
Lardon3DImageSort sort = lardon3d_image_view_sort(state->image_view);
|
||||
char filter[LARDON3D_IMAGE_FILTER_CAPACITY];
|
||||
(void)snprintf(
|
||||
filter,
|
||||
sizeof(filter),
|
||||
"%s",
|
||||
lardon3d_image_view_filter(state->image_view)
|
||||
);
|
||||
size_t selected_catalog = 0;
|
||||
bool preserve_selection = lardon3d_image_view_catalog_index(
|
||||
state->image_view,
|
||||
lardon3d_image_view_selection(state->image_view),
|
||||
&selected_catalog
|
||||
);
|
||||
lardon3d_image_view_destroy(state->image_view);
|
||||
state->image_view = NULL;
|
||||
lardon3d_image_catalog_destroy(state->image_catalog);
|
||||
state->image_catalog = NULL;
|
||||
char message[sizeof(state->status_message)];
|
||||
char message[sizeof(state->status_message)] = "";
|
||||
state->image_catalog = lardon3d_image_catalog_load(
|
||||
state,
|
||||
message,
|
||||
sizeof(message)
|
||||
);
|
||||
normalize_catalog_navigation(state);
|
||||
if (!state->image_catalog || message[0]) {
|
||||
if (!state->image_catalog) {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"%s",
|
||||
message
|
||||
);
|
||||
return state->image_catalog != NULL;
|
||||
return false;
|
||||
}
|
||||
char catalog_message[sizeof(message)];
|
||||
(void)snprintf(catalog_message, sizeof(catalog_message), "%s", message);
|
||||
char view_error[sizeof(message)];
|
||||
state->image_view = lardon3d_image_view_create(state->image_catalog);
|
||||
if (!state->image_view
|
||||
|| !lardon3d_image_view_set_sort(state->image_view, sort)
|
||||
|| !lardon3d_image_view_set_filter(
|
||||
state->image_view,
|
||||
filter,
|
||||
view_error,
|
||||
sizeof(view_error)
|
||||
)) {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Erreur : impossible de construire la vue des images."
|
||||
);
|
||||
lardon3d_image_view_destroy(state->image_view);
|
||||
state->image_view = NULL;
|
||||
return false;
|
||||
}
|
||||
if (preserve_selection) {
|
||||
size_t count = lardon3d_image_view_count(state->image_view);
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
size_t catalog_index;
|
||||
if (lardon3d_image_view_catalog_index(
|
||||
state->image_view,
|
||||
index,
|
||||
&catalog_index
|
||||
)
|
||||
&& catalog_index == selected_catalog) {
|
||||
lardon3d_image_view_select(
|
||||
state->image_view,
|
||||
index,
|
||||
catalog_page_size()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
normalize_view_navigation(state);
|
||||
if (catalog_message[0]) {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"%s",
|
||||
catalog_message
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (announce_success) {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Catalogue rechargé : %zu image%s.",
|
||||
lardon3d_image_catalog_count(state->image_catalog),
|
||||
lardon3d_image_catalog_count(state->image_catalog) == 1 ? "" : "s"
|
||||
lardon3d_image_view_count(state->image_view),
|
||||
lardon3d_image_view_count(state->image_view) == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -109,6 +162,8 @@ redraw(
|
|||
label = "Nom du dossier projet :";
|
||||
} else if (input->mode == INPUT_IMPORT_DIRECTORY) {
|
||||
label = "Dossier source :";
|
||||
} else if (input->mode == INPUT_IMAGE_FILTER) {
|
||||
label = "Filtre :";
|
||||
}
|
||||
Lardon3DImportTaskSnapshot snapshot;
|
||||
const Lardon3DImportTaskSnapshot *displayed_snapshot = NULL;
|
||||
|
|
@ -187,6 +242,8 @@ handle_active_input(
|
|||
message = "Ouverture du projet annulée.";
|
||||
} else if (input->mode == INPUT_IMPORT_DIRECTORY) {
|
||||
message = "Import annulé.";
|
||||
} else if (input->mode == INPUT_IMAGE_FILTER) {
|
||||
message = "Filtre annulé.";
|
||||
}
|
||||
input->mode = INPUT_NONE;
|
||||
(void)snprintf(
|
||||
|
|
@ -205,6 +262,37 @@ handle_active_input(
|
|||
}
|
||||
} else if (input->mode == INPUT_IMPORT_DIRECTORY) {
|
||||
(void)start_import_task(state, input, task);
|
||||
} else if (input->mode == INPUT_IMAGE_FILTER) {
|
||||
char message[sizeof(state->status_message)];
|
||||
if (lardon3d_image_view_set_filter(
|
||||
state->image_view,
|
||||
input->text,
|
||||
message,
|
||||
sizeof(message)
|
||||
)) {
|
||||
normalize_view_navigation(state);
|
||||
if (lardon3d_image_view_count(state->image_view) == 0) {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Aucune image ne correspond au filtre."
|
||||
);
|
||||
} else {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Filtre appliqué : %.230s",
|
||||
input->text
|
||||
);
|
||||
}
|
||||
} else {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"%s",
|
||||
message
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (lardon3d_project_create(state, input->text)) {
|
||||
(void)reload_catalog(state, false);
|
||||
|
|
@ -223,7 +311,18 @@ handle_active_input(
|
|||
}
|
||||
|
||||
if (key >= 0 && key <= UCHAR_MAX && isprint((unsigned char)key)) {
|
||||
if (input->length + 1 >= sizeof(input->text)) {
|
||||
size_t capacity = input->mode == INPUT_IMAGE_FILTER
|
||||
? LARDON3D_IMAGE_FILTER_CAPACITY
|
||||
: sizeof(input->text);
|
||||
if (input->length + 1 >= capacity) {
|
||||
if (input->mode == INPUT_IMAGE_FILTER) {
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Erreur : filtre trop long."
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (input->mode == INPUT_PROJECT_OPEN) {
|
||||
if (lardon3d_project_open(state, input->text)) {
|
||||
(void)reload_catalog(state, false);
|
||||
|
|
@ -301,24 +400,31 @@ handle_normal_input(
|
|||
state->screen = LARDON3D_SCREEN_HOME;
|
||||
return true;
|
||||
case KEY_RESIZE:
|
||||
normalize_catalog_navigation(state);
|
||||
normalize_view_navigation(state);
|
||||
return true;
|
||||
case KEY_UP:
|
||||
case 'k':
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT
|
||||
&& state->image_selection > 0) {
|
||||
--state->image_selection;
|
||||
normalize_catalog_navigation(state);
|
||||
&& lardon3d_image_view_selection(state->image_view) > 0) {
|
||||
lardon3d_image_view_select(
|
||||
state->image_view,
|
||||
lardon3d_image_view_selection(state->image_view) - 1,
|
||||
catalog_page_size()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case KEY_DOWN:
|
||||
case 'j': {
|
||||
size_t count = lardon3d_image_catalog_count(state->image_catalog);
|
||||
size_t count = lardon3d_image_view_count(state->image_view);
|
||||
size_t selection = lardon3d_image_view_selection(state->image_view);
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT
|
||||
&& state->image_selection + 1 < count) {
|
||||
++state->image_selection;
|
||||
normalize_catalog_navigation(state);
|
||||
&& selection < count && selection + 1 < count) {
|
||||
lardon3d_image_view_select(
|
||||
state->image_view,
|
||||
selection + 1,
|
||||
catalog_page_size()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -326,39 +432,53 @@ handle_normal_input(
|
|||
case KEY_PPAGE:
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT) {
|
||||
size_t page_size = catalog_page_size();
|
||||
state->image_selection = state->image_selection > page_size
|
||||
? state->image_selection - page_size
|
||||
size_t selection = lardon3d_image_view_selection(state->image_view);
|
||||
selection = selection > page_size
|
||||
? selection - page_size
|
||||
: 0;
|
||||
normalize_catalog_navigation(state);
|
||||
lardon3d_image_view_select(state->image_view, selection, page_size);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case KEY_NPAGE:
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT) {
|
||||
size_t count = lardon3d_image_catalog_count(state->image_catalog);
|
||||
size_t count = lardon3d_image_view_count(state->image_view);
|
||||
if (count > 0) {
|
||||
size_t remaining = count - 1 - state->image_selection;
|
||||
size_t selection = lardon3d_image_view_selection(
|
||||
state->image_view
|
||||
);
|
||||
size_t remaining = count - 1 - selection;
|
||||
size_t page_size = catalog_page_size();
|
||||
state->image_selection += remaining < page_size
|
||||
selection += remaining < page_size
|
||||
? remaining
|
||||
: page_size;
|
||||
normalize_catalog_navigation(state);
|
||||
lardon3d_image_view_select(
|
||||
state->image_view,
|
||||
selection,
|
||||
page_size
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case KEY_HOME:
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT) {
|
||||
state->image_selection = 0;
|
||||
normalize_catalog_navigation(state);
|
||||
lardon3d_image_view_select(
|
||||
state->image_view,
|
||||
0,
|
||||
catalog_page_size()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case KEY_END:
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT) {
|
||||
size_t count = lardon3d_image_catalog_count(state->image_catalog);
|
||||
state->image_selection = count > 0 ? count - 1 : 0;
|
||||
normalize_catalog_navigation(state);
|
||||
size_t count = lardon3d_image_view_count(state->image_view);
|
||||
lardon3d_image_view_select(
|
||||
state->image_view,
|
||||
count > 0 ? count - 1 : 0,
|
||||
catalog_page_size()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -418,6 +538,59 @@ handle_normal_input(
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
case 's':
|
||||
case 'S':
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT && state->image_view) {
|
||||
Lardon3DImageSort sort = lardon3d_image_view_sort(
|
||||
state->image_view
|
||||
);
|
||||
sort = (Lardon3DImageSort)(
|
||||
((int)sort + 1) % ((int)LARDON3D_IMAGE_SORT_SIZE_DESC + 1)
|
||||
);
|
||||
if (lardon3d_image_view_set_sort(state->image_view, sort)) {
|
||||
normalize_view_navigation(state);
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Tri : %s.",
|
||||
lardon3d_image_view_sort_name(sort)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case '/':
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT && state->image_view) {
|
||||
const char *filter = lardon3d_image_view_filter(state->image_view);
|
||||
*input = (TuiInput) {
|
||||
.mode = INPUT_IMAGE_FILTER,
|
||||
.text = "",
|
||||
.length = strlen(filter),
|
||||
};
|
||||
(void)snprintf(input->text, sizeof(input->text), "%s", filter);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 'x':
|
||||
case 'X':
|
||||
if (state->screen == LARDON3D_SCREEN_IMPORT && state->image_view) {
|
||||
char message[sizeof(state->status_message)];
|
||||
if (lardon3d_image_view_set_filter(
|
||||
state->image_view,
|
||||
"",
|
||||
message,
|
||||
sizeof(message)
|
||||
)) {
|
||||
normalize_view_navigation(state);
|
||||
(void)snprintf(
|
||||
state->status_message,
|
||||
sizeof(state->status_message),
|
||||
"Filtre effacé."
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 'c':
|
||||
case 'C':
|
||||
if (state->screen == LARDON3D_SCREEN_PROJECTS) {
|
||||
|
|
|
|||
316
tests/test_image_view.c
Normal file
316
tests/test_image_view.c
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <lardon3d/app_state.h>
|
||||
#include <lardon3d/image_catalog.h>
|
||||
#include <lardon3d/image_view.h>
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
|
||||
return false; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static bool
|
||||
join_path(char destination[PATH_MAX], const char *parent, const char *child)
|
||||
{
|
||||
int written = snprintf(destination, PATH_MAX, "%s/%s", parent, child);
|
||||
return written >= 0 && (size_t)written < PATH_MAX;
|
||||
}
|
||||
|
||||
static bool
|
||||
create_file(const char *path, size_t size)
|
||||
{
|
||||
int descriptor = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||||
if (descriptor < 0) {
|
||||
return false;
|
||||
}
|
||||
char bytes[16] = {0};
|
||||
size_t remaining = size;
|
||||
while (remaining > 0) {
|
||||
size_t length = remaining < sizeof(bytes) ? remaining : sizeof(bytes);
|
||||
ssize_t written = write(descriptor, bytes, length);
|
||||
if (written < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (written <= 0) {
|
||||
(void)close(descriptor);
|
||||
return false;
|
||||
}
|
||||
remaining -= (size_t)written;
|
||||
}
|
||||
return close(descriptor) == 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
write_manifest(const char *path, const char *content)
|
||||
{
|
||||
int descriptor = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||||
if (descriptor < 0) {
|
||||
return false;
|
||||
}
|
||||
size_t length = strlen(content);
|
||||
size_t total = 0;
|
||||
while (total < length) {
|
||||
ssize_t written = write(descriptor, content + total, length - total);
|
||||
if (written < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (written <= 0) {
|
||||
(void)close(descriptor);
|
||||
return false;
|
||||
}
|
||||
total += (size_t)written;
|
||||
}
|
||||
return close(descriptor) == 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
remove_tree(const char *path)
|
||||
{
|
||||
struct stat info;
|
||||
if (lstat(path, &info) != 0) {
|
||||
return errno == ENOENT;
|
||||
}
|
||||
if (!S_ISDIR(info.st_mode)) {
|
||||
return unlink(path) == 0;
|
||||
}
|
||||
DIR *directory = opendir(path);
|
||||
if (!directory) {
|
||||
return false;
|
||||
}
|
||||
bool success = true;
|
||||
for (struct dirent *entry = readdir(directory);
|
||||
entry;
|
||||
entry = readdir(directory)) {
|
||||
if (strcmp(entry->d_name, ".") == 0
|
||||
|| strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
char child[PATH_MAX];
|
||||
if (!join_path(child, path, entry->d_name) || !remove_tree(child)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if (closedir(directory) != 0 || rmdir(path) != 0) {
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static Lardon3DImageCatalog *
|
||||
load_catalog(const char *project)
|
||||
{
|
||||
Lardon3DAppState state;
|
||||
lardon3d_app_state_init(&state);
|
||||
state.project_loaded = true;
|
||||
(void)snprintf(state.project_path, sizeof(state.project_path), "%s", project);
|
||||
char error[256];
|
||||
return lardon3d_image_catalog_load(&state, error, sizeof(error));
|
||||
}
|
||||
|
||||
static bool
|
||||
expect_names(
|
||||
const Lardon3DImageView *view,
|
||||
const char *const *names,
|
||||
size_t count
|
||||
)
|
||||
{
|
||||
CHECK(lardon3d_image_view_count(view) == count);
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
const Lardon3DImageEntry *entry = lardon3d_image_view_get(view, index);
|
||||
CHECK(entry && strcmp(entry->filename, names[index]) == 0);
|
||||
}
|
||||
CHECK(lardon3d_image_view_get(view, count) == NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
run_test(void)
|
||||
{
|
||||
char base[] = "/tmp/lardon3d-image-view.XXXXXX";
|
||||
CHECK(mkdtemp(base));
|
||||
char project[PATH_MAX];
|
||||
char images[PATH_MAX];
|
||||
char originals[PATH_MAX];
|
||||
char manifest[PATH_MAX];
|
||||
CHECK(join_path(project, base, "project"));
|
||||
CHECK(join_path(images, project, "images"));
|
||||
CHECK(join_path(originals, images, "originals"));
|
||||
CHECK(join_path(manifest, images, "manifest.tsv"));
|
||||
CHECK(mkdir(project, 0755) == 0);
|
||||
CHECK(mkdir(images, 0755) == 0);
|
||||
CHECK(mkdir(originals, 0755) == 0);
|
||||
|
||||
Lardon3DImageCatalog *empty_catalog = load_catalog(project);
|
||||
CHECK(empty_catalog);
|
||||
Lardon3DImageView *view = lardon3d_image_view_create(empty_catalog);
|
||||
CHECK(view);
|
||||
CHECK(lardon3d_image_view_count(view) == 0);
|
||||
CHECK(lardon3d_image_view_total_size(view) == 0);
|
||||
lardon3d_image_view_normalize(view, 0);
|
||||
CHECK(lardon3d_image_view_selection(view) == 0);
|
||||
CHECK(lardon3d_image_view_offset(view) == 0);
|
||||
lardon3d_image_view_destroy(view);
|
||||
lardon3d_image_catalog_destroy(empty_catalog);
|
||||
lardon3d_image_view_destroy(NULL);
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
size_t size;
|
||||
} files[] = {
|
||||
{"Zulu.JPG", 5},
|
||||
{"alpha.png", 2},
|
||||
{"Beta.jpg", 5},
|
||||
{"été.png", 3},
|
||||
{"motor final.TIF", 8},
|
||||
};
|
||||
for (size_t index = 0; index < sizeof(files) / sizeof(files[0]); ++index) {
|
||||
char path[PATH_MAX];
|
||||
CHECK(join_path(path, originals, files[index].name));
|
||||
CHECK(create_file(path, files[index].size));
|
||||
}
|
||||
CHECK(write_manifest(
|
||||
manifest,
|
||||
"filename\tsize_bytes\tsource_path\n"
|
||||
"Zulu.JPG\t5\t/tmp/source/Zulu.JPG\n"
|
||||
"alpha.png\t2\t/tmp/source/alpha.png\n"
|
||||
"Beta.jpg\t5\t/tmp/source/Beta.jpg\n"
|
||||
"été.png\t3\t/tmp/source avec espaces/été.png\n"
|
||||
"motor final.TIF\t8\t/tmp/source/motor final.TIF\n"
|
||||
));
|
||||
Lardon3DImageCatalog *catalog = load_catalog(project);
|
||||
CHECK(catalog && lardon3d_image_catalog_count(catalog) == 5);
|
||||
const Lardon3DImageEntry *original_entries[5];
|
||||
for (size_t index = 0; index < 5; ++index) {
|
||||
original_entries[index] = lardon3d_image_catalog_get(catalog, index);
|
||||
}
|
||||
view = lardon3d_image_view_create(catalog);
|
||||
CHECK(view);
|
||||
|
||||
static const char *const import_order[] = {
|
||||
"Zulu.JPG", "alpha.png", "Beta.jpg", "été.png", "motor final.TIF",
|
||||
};
|
||||
static const char *const name_ascending[] = {
|
||||
"Beta.jpg", "Zulu.JPG", "alpha.png", "motor final.TIF", "été.png",
|
||||
};
|
||||
static const char *const name_descending[] = {
|
||||
"été.png", "motor final.TIF", "alpha.png", "Zulu.JPG", "Beta.jpg",
|
||||
};
|
||||
static const char *const size_ascending[] = {
|
||||
"alpha.png", "été.png", "Beta.jpg", "Zulu.JPG", "motor final.TIF",
|
||||
};
|
||||
static const char *const size_descending[] = {
|
||||
"motor final.TIF", "Beta.jpg", "Zulu.JPG", "été.png", "alpha.png",
|
||||
};
|
||||
CHECK(expect_names(view, import_order, 5));
|
||||
CHECK(lardon3d_image_view_total_size(view) == 23);
|
||||
CHECK(lardon3d_image_view_rebuild(view));
|
||||
CHECK(expect_names(view, import_order, 5));
|
||||
CHECK(lardon3d_image_view_set_sort(view, LARDON3D_IMAGE_SORT_NAME_ASC));
|
||||
CHECK(expect_names(view, name_ascending, 5));
|
||||
CHECK(lardon3d_image_view_set_sort(view, LARDON3D_IMAGE_SORT_NAME_DESC));
|
||||
CHECK(expect_names(view, name_descending, 5));
|
||||
CHECK(lardon3d_image_view_set_sort(view, LARDON3D_IMAGE_SORT_SIZE_ASC));
|
||||
CHECK(expect_names(view, size_ascending, 5));
|
||||
CHECK(lardon3d_image_view_set_sort(view, LARDON3D_IMAGE_SORT_SIZE_DESC));
|
||||
CHECK(expect_names(view, size_descending, 5));
|
||||
CHECK(!lardon3d_image_view_set_sort(view, (Lardon3DImageSort)99));
|
||||
|
||||
char error[256];
|
||||
CHECK(lardon3d_image_view_set_filter(view, "", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_count(view) == 5);
|
||||
CHECK(lardon3d_image_view_set_filter(view, "JPG", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_count(view) == 2);
|
||||
CHECK(lardon3d_image_view_total_size(view) == 10);
|
||||
CHECK(lardon3d_image_view_ascii_contains("Alpha.JPEG", "pHa.j"));
|
||||
CHECK(!lardon3d_image_view_ascii_contains("été.png", "ÉTÉ"));
|
||||
CHECK(lardon3d_image_view_set_filter(view, "été", error, sizeof(error)));
|
||||
CHECK(expect_names(view, (const char *const[]){"été.png"}, 1));
|
||||
CHECK(lardon3d_image_view_set_filter(view, "absent", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_count(view) == 0);
|
||||
CHECK(lardon3d_image_view_selection(view) == 0);
|
||||
CHECK(lardon3d_image_view_offset(view) == 0);
|
||||
|
||||
CHECK(lardon3d_image_view_set_filter(view, "", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_set_sort(view, LARDON3D_IMAGE_SORT_IMPORT_ORDER));
|
||||
lardon3d_image_view_select(view, 2, 2);
|
||||
size_t selected_catalog;
|
||||
CHECK(lardon3d_image_view_catalog_index(view, 2, &selected_catalog));
|
||||
CHECK(selected_catalog == 2);
|
||||
CHECK(!lardon3d_image_view_catalog_index(view, 99, &selected_catalog));
|
||||
CHECK(lardon3d_image_view_set_sort(view, LARDON3D_IMAGE_SORT_NAME_ASC));
|
||||
CHECK(strcmp(
|
||||
lardon3d_image_view_get(
|
||||
view,
|
||||
lardon3d_image_view_selection(view)
|
||||
)->filename,
|
||||
"Beta.jpg"
|
||||
) == 0);
|
||||
CHECK(lardon3d_image_view_set_filter(view, "beta", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_selection(view) == 0);
|
||||
CHECK(lardon3d_image_view_set_filter(view, "alpha", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_selection(view) == 0);
|
||||
CHECK(strcmp(lardon3d_image_view_get(view, 0)->filename, "alpha.png") == 0);
|
||||
|
||||
char too_long[LARDON3D_IMAGE_FILTER_CAPACITY + 1];
|
||||
(void)memset(too_long, 'a', sizeof(too_long) - 1);
|
||||
too_long[sizeof(too_long) - 1] = '\0';
|
||||
CHECK(!lardon3d_image_view_set_filter(view, too_long, error, sizeof(error)));
|
||||
CHECK(error[0]);
|
||||
CHECK(strcmp(lardon3d_image_view_filter(view), "alpha") == 0);
|
||||
|
||||
for (size_t index = 0; index < 5; ++index) {
|
||||
CHECK(lardon3d_image_catalog_get(catalog, index) == original_entries[index]);
|
||||
CHECK(strcmp(original_entries[index]->filename, import_order[index]) == 0);
|
||||
}
|
||||
|
||||
char small_project[PATH_MAX];
|
||||
char small_images[PATH_MAX];
|
||||
char small_originals[PATH_MAX];
|
||||
char small_manifest[PATH_MAX];
|
||||
CHECK(join_path(small_project, base, "small"));
|
||||
CHECK(join_path(small_images, small_project, "images"));
|
||||
CHECK(join_path(small_originals, small_images, "originals"));
|
||||
CHECK(join_path(small_manifest, small_images, "manifest.tsv"));
|
||||
CHECK(mkdir(small_project, 0755) == 0);
|
||||
CHECK(mkdir(small_images, 0755) == 0);
|
||||
CHECK(mkdir(small_originals, 0755) == 0);
|
||||
char only[PATH_MAX];
|
||||
CHECK(join_path(only, small_originals, "only.jpg"));
|
||||
CHECK(create_file(only, 1));
|
||||
CHECK(write_manifest(
|
||||
small_manifest,
|
||||
"filename\tsize_bytes\tsource_path\nonly.jpg\t1\t/tmp/only.jpg\n"
|
||||
));
|
||||
Lardon3DImageCatalog *small_catalog = load_catalog(small_project);
|
||||
CHECK(small_catalog);
|
||||
CHECK(lardon3d_image_view_set_filter(view, "", error, sizeof(error)));
|
||||
CHECK(lardon3d_image_view_set_catalog(view, small_catalog));
|
||||
CHECK(lardon3d_image_view_count(view) == 1);
|
||||
CHECK(strcmp(lardon3d_image_view_get(view, 0)->filename, "only.jpg") == 0);
|
||||
lardon3d_image_view_select(view, 100, 3);
|
||||
CHECK(lardon3d_image_view_selection(view) == 0);
|
||||
CHECK(lardon3d_image_view_offset(view) == 0);
|
||||
|
||||
lardon3d_image_view_destroy(view);
|
||||
lardon3d_image_catalog_destroy(small_catalog);
|
||||
lardon3d_image_catalog_destroy(catalog);
|
||||
CHECK(remove_tree(base));
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Loading…
Reference in a new issue