#include "views/create_person_dialog.h" #include "core/file_hash.h" #include "core/person_creation_coordinator.h" #include "dao/evidence_dao.h" #include "dao/identity_ocr_dao.h" #include "database/database.h" #include "database/statement.h" #include #include #include #include typedef struct { GtkApplication *application; GtkWindow *main_window; GtkWindow *dialog; TaskManager *task_manager; ToolRegistry *registry; char *root; char *database_path; guint phase; guint guard; guint completion_count; guint layout_index; guint confirmation_stage; int confirmation_width; int confirmation_height; gboolean layout_resize_pending; gboolean passed; gboolean persisted; } OcrGtkContext; static void remove_tree(const char *root) { GDir *directory=g_dir_open(root,0,NULL);const char *name; if(directory==NULL)return; while((name=g_dir_read_name(directory))!=NULL){ char *path=g_build_filename(root,name,NULL); if(g_file_test(path,G_FILE_TEST_IS_DIR))remove_tree(path); else g_unlink(path); g_free(path); } g_dir_close(directory);g_rmdir(root); } static GtkWidget *find_button(GtkWidget *widget, const char *label) { if (GTK_IS_BUTTON(widget) && g_strcmp0(gtk_button_get_label(GTK_BUTTON(widget)), label) == 0) return widget; for (GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)) { GtkWidget *match=find_button(child,label); if (match!=NULL)return match; } return NULL; } static GtkWidget *find_named(GtkWidget *widget,const char *name) { if(g_strcmp0(gtk_widget_get_name(widget),name)==0)return widget; for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)){ GtkWidget *match=find_named(child,name); if(match!=NULL)return match; } return NULL; } static GtkWidget *find_ocr_field_row(GtkWidget *widget, const char *code, guint order) { const char *widget_code = g_object_get_data( G_OBJECT(widget), "identity-field-code"); guint widget_order = GPOINTER_TO_UINT(g_object_get_data( G_OBJECT(widget), "identity-field-order")); if (g_strcmp0(widget_code, code) == 0 && widget_order == order + 1) return widget; for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)){ GtkWidget *match=find_ocr_field_row(child,code,order); if(match!=NULL)return match; } return NULL; } static guint count_ocr_field_rows(GtkWidget *widget) { guint count=g_object_get_data( G_OBJECT(widget),"identity-field-code")!=NULL?1:0; for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)) count+=count_ocr_field_rows(child); return count; } static GtkWidget *find_entry(GtkWidget *widget,const char *placeholder) { if(GTK_IS_ENTRY(widget)&&g_strcmp0( gtk_entry_get_placeholder_text(GTK_ENTRY(widget)),placeholder)==0) return widget; for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)){ GtkWidget *match=find_entry(child,placeholder); if(match!=NULL)return match; } return NULL; } static GtkWidget *find_label(GtkWidget *widget,const char *text); static void diagnose_ocr_field(GtkWidget *root, guint phase, const char *code, guint order, GtkWidget *row) { GtkWidget *entry=row!=NULL?g_object_get_data( G_OBJECT(row),"identity-value-entry"):NULL; GtkWidget *modify=row!=NULL?g_object_get_data( G_OBJECT(row),"identity-modify-button"):NULL; GtkWidget *reedit=row!=NULL?g_object_get_data( G_OBJECT(row),"identity-reedit-button"):NULL; const char *task_state=find_label(root,"OCR terminé")!=NULL ?"terminée":"en cours ou en échec"; g_printerr("Diagnostic OCR: étape=%u propositions=%u " "code=%s ordre=%u ligne=%s entrée=%s modifier=%s " "rééditer=%s tâche=%s\n",phase,count_ocr_field_rows(root),code,order, row!=NULL?"oui":"non",entry!=NULL?"oui":"non", modify!=NULL?"oui":"non",reedit!=NULL?"oui":"non",task_state); } static GtkDropDown *find_evidence_dropdown(GtkWidget *widget) { if(GTK_IS_DROP_DOWN(widget)){ GListModel *model=gtk_drop_down_get_model(GTK_DROP_DOWN(widget)); GtkStringObject *first=model!=NULL&&g_list_model_get_n_items(model)>0 ?g_list_model_get_item(model,0):NULL; gboolean match=first!=NULL&&g_strcmp0( gtk_string_object_get_string(first),"Aucune preuve associée")==0; g_clear_object(&first);if(match)return GTK_DROP_DOWN(widget); } for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)){ GtkDropDown *match=find_evidence_dropdown(child); if(match!=NULL)return match; } return NULL; } static GtkSpinButton *find_visible_spin(GtkWidget *widget) { if(GTK_IS_SPIN_BUTTON(widget)&>k_widget_get_mapped(widget)) return GTK_SPIN_BUTTON(widget); for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)){ GtkSpinButton *match=find_visible_spin(child); if(match!=NULL)return match; } return NULL; } static GtkWidget *find_label(GtkWidget *widget,const char *text) { if(GTK_IS_LABEL(widget)&&strstr(gtk_label_get_text(GTK_LABEL(widget)), text)!=NULL)return widget; for(GtkWidget *child=gtk_widget_get_first_child(widget);child!=NULL; child=gtk_widget_get_next_sibling(child)){ GtkWidget *match=find_label(child,text); if(match!=NULL)return match; } return NULL; } static char *text_view_contents(GtkTextView *view) { GtkTextIter start,end;GtkTextBuffer *buffer=gtk_text_view_get_buffer(view); gtk_text_buffer_get_bounds(buffer,&start,&end); return gtk_text_buffer_get_text(buffer,&start,&end,FALSE); } static guint64 table_count(const char *database_path,const char *table) { Database *database=database_open(database_path); char *sql=g_strdup_printf("SELECT COUNT(*) FROM %s;",table); DatabaseStatement *statement=database_statement_prepare(database,sql); int64_t value=0;g_assert_nonnull(statement); g_assert_cmpint(database_statement_step(statement),==, DATABASE_STATEMENT_STEP_ROW); g_assert_true(database_statement_column_int64(statement,0,&value)); database_statement_finalize(statement);database_close(database); g_free(sql);return (guint64)value; } static void verify_persisted_review(OcrGtkContext *context) { GError *error=NULL; Database *database=database_open(context->database_path); IdentityOcrDao *dao=identity_ocr_dao_new(database); GPtrArray *runs=identity_ocr_dao_list_runs_by_evidence(dao, "10000000-0000-4000-8000-000000000002",&error); g_assert_no_error(error);g_assert_cmpuint(runs->len,==,1); IdentityOcrRunRecord *record=g_ptr_array_index(runs,0); IdentityOcrRun *run=identity_ocr_dao_load_run(dao,context->root, record->id,NULL,&error); g_assert_no_error(error);g_assert_nonnull(run); g_assert_cmpstr(identity_ocr_run_get_corrected_transcription(run),==, "TRANSCRIPTION PERSONNE SPECIMEN CORRIGÉE"); g_assert_nonnull(strstr(identity_ocr_run_get_factual_notes(run), "note documentaire SPECIMEN")); const GPtrArray *fields=identity_ocr_run_get_fields(run); gboolean modified=FALSE,manual=FALSE; for(guint index=0;indexlen;index++){ IdentityFieldObservation *field=g_ptr_array_index( (GPtrArray *)fields,index); modified|=g_strcmp0( identity_field_observation_get_corrected_value(field), "PAGE 2 VALEUR FINALE")==0&& g_strcmp0(identity_field_observation_get_origin(field), "manual_override")==0; manual|=g_strcmp0( identity_field_observation_get_corrected_value(field), "MANUEL SPECIMEN MODIFIÉ")==0&& g_strcmp0(identity_field_observation_get_origin(field), "manual_entry")==0; } g_assert_true(modified);g_assert_true(manual); identity_ocr_run_free(run);g_ptr_array_unref(runs); identity_ocr_dao_free(dao);database_close(database); } static void completed(CreatePersonDialogResult *result,gpointer data) { OcrGtkContext *context=data; if(result!=NULL){ GError *error=NULL; Database *database=database_open(context->database_path); PersonCreationCoordinatorResult *created= person_creation_coordinator_execute(database,context->root, create_person_dialog_result_get_input(result), create_person_dialog_result_get_evidence_selection(result), create_person_dialog_result_get_ocr_runs(result), NULL,&error); g_assert_no_error(error);g_assert_nonnull(created); person_creation_coordinator_result_free(created); database_close(database); create_person_dialog_result_free(result); context->persisted=TRUE; } context->completion_count++; } static gboolean drive(gpointer data) { OcrGtkContext *context=data; GtkWidget *root=context->phase<4?GTK_WIDGET(context->dialog):NULL; GtkWidget *button;char *text; if(++context->guard>1000)g_error("Timeout du parcours OCR GTK"); switch(context->phase){ case 0: { static const int widths[]={1536,1366,1200,760}; static const int heights[]={864,768,800,560}; if(context->layout_indexlayout_resize_pending){ gtk_window_set_default_size(context->dialog, widths[context->layout_index], heights[context->layout_index]); context->layout_resize_pending=TRUE; return G_SOURCE_CONTINUE; } int width=0,height=0; gtk_window_get_default_size(context->dialog,&width,&height); g_assert_cmpint(width,==,widths[context->layout_index]); g_assert_cmpint(height,==,heights[context->layout_index]); GtkWidget *paned=find_named(root, "create-person-ocr-paned"); GtkWidget *scroll=find_named(root, "create-person-ocr-scroll"); GtkWidget *actions=find_named(root, "create-person-actions"); g_assert_true(GTK_IS_PANED(paned)); g_assert_true(GTK_IS_SCROLLED_WINDOW(scroll)); GtkPolicyType horizontal_policy=GTK_POLICY_ALWAYS; g_object_get(scroll,"hscrollbar-policy", &horizontal_policy,NULL); g_assert_cmpint(horizontal_policy,==,GTK_POLICY_NEVER); g_assert_true(gtk_widget_get_mapped(actions)); if(context->layout_index==0){ int position=gtk_paned_get_position(GTK_PANED(paned)); int allocated=gtk_widget_get_width(paned); g_assert_cmpint(position,>=,480); g_assert_cmpint(allocated-position,>=,240); g_assert_cmpint(position,<,allocated-allocated/10); } context->layout_index++; context->layout_resize_pending=FALSE; return G_SOURCE_CONTINUE; } GtkStack *transcription=GTK_STACK(find_named(root, "create-person-ocr-transcription-stack")); g_assert_true(GTK_IS_STACK(transcription)); gtk_stack_set_visible_child_name(transcription,"corrected"); g_assert_cmpstr(gtk_stack_get_visible_child_name(transcription), ==,"corrected"); gtk_stack_set_visible_child_name(transcription,"raw"); g_assert_cmpstr(gtk_stack_get_visible_child_name(transcription), ==,"raw"); } { GtkDropDown *languages=GTK_DROP_DOWN(find_named(root, "create-person-ocr-languages")); g_assert_true(GTK_IS_DROP_DOWN(languages)); guint selected=gtk_drop_down_get_selected(languages); GListModel *model=gtk_drop_down_get_model(languages); GtkStringObject *selected_item=g_list_model_get_item( model,selected); g_assert_nonnull(selected_item); g_assert_cmpstr(gtk_string_object_get_string(selected_item),==, "Français (fra)"); g_object_unref(selected_item); } if(find_button(root,"Analyser comme document d’identité")==NULL) return G_SOURCE_CONTINUE; if(!gtk_widget_get_sensitive(find_button(root, "Analyser comme document d’identité")))return G_SOURCE_CONTINUE; g_signal_emit_by_name(find_button(root, "Analyser comme document d’identité"),"clicked"); g_assert_nonnull(find_label(root,"OCR contrôlé en cours")); context->phase++;break; case 1: if(find_label(root,"OCR terminé")==NULL)return G_SOURCE_CONTINUE; text=text_view_contents(GTK_TEXT_VIEW(find_named( root,"create-person-ocr-raw-text"))); g_assert_nonnull(strstr(text,"NOM : SPECIMEN"));g_free(text); g_assert_nonnull(find_button(root,"Voir la zone")); g_assert_cmpuint(count_ocr_field_rows(root),>=,3); GtkWidget *raw_row=find_ocr_field_row(root,"nationality",3); diagnose_ocr_field(root,context->phase,"nationality",3,raw_row); g_assert_nonnull(raw_row); GtkWidget *raw_entry=g_object_get_data( G_OBJECT(raw_row),"identity-value-entry"); GtkWidget *raw_modify=g_object_get_data( G_OBJECT(raw_row),"identity-modify-button"); g_assert_nonnull(raw_entry);g_assert_nonnull(raw_modify); g_assert_true(gtk_editable_get_editable(GTK_EDITABLE(raw_entry))); gtk_editable_set_text(GTK_EDITABLE(raw_entry),"FRANÇAIS"); g_signal_emit_by_name(raw_modify,"clicked"); g_assert_nonnull(find_button(raw_row,"Modifiée")); g_assert_false(gtk_editable_get_editable(GTK_EDITABLE(raw_entry))); g_signal_emit_by_name(g_object_get_data( G_OBJECT(raw_row),"identity-reedit-button"),"clicked"); g_assert_true(gtk_editable_get_editable(GTK_EDITABLE(raw_entry))); gtk_editable_set_text(GTK_EDITABLE(raw_entry),"FRANÇAIS CORRIGÉ"); g_signal_emit_by_name(raw_modify,"clicked"); g_assert_nonnull(find_label(raw_row, "Valeur OCR brute : FRANCAIS")); g_signal_emit_by_name(find_button(raw_row, "Revenir à la valeur OCR"),"clicked"); g_assert_cmpstr(gtk_editable_get_text(GTK_EDITABLE(raw_entry)),==, "FRANCAIS"); gtk_editable_set_text(GTK_EDITABLE(raw_entry),"FRANÇAIS FINAL"); g_signal_emit_by_name(raw_modify,"clicked"); g_signal_emit_by_name(find_button(raw_row,"Rejeter"),"clicked"); g_signal_emit_by_name(find_button(raw_row, "Modifier à nouveau"),"clicked"); g_signal_emit_by_name(raw_modify,"clicked"); GtkWidget *manual_entry=find_entry(root, "Valeur réellement visible dans la preuve"); g_assert_nonnull(manual_entry); gtk_editable_set_text(GTK_EDITABLE(manual_entry), "DOCUMENT VISIBLE SPECIMEN"); g_signal_emit_by_name(find_button(root, "Ajouter un champ manquant"),"clicked"); GtkWidget *manual_row=find_ocr_field_row(root,"document_type",4); diagnose_ocr_field(root,context->phase,"document_type",4,manual_row); g_assert_nonnull(manual_row); g_assert_nonnull(find_label(manual_row,"Origine : manual_entry")); g_signal_emit_by_name(g_object_get_data( G_OBJECT(manual_row),"identity-modify-button"),"clicked"); GtkTextView *notes=GTK_TEXT_VIEW(find_named( root,"create-person-ocr-factual-notes")); g_assert_nonnull(notes); gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes), "document tronqué", -1); g_signal_emit_by_name(find_button(root,"Voir la zone"),"clicked"); g_assert_true(create_person_dialog_test_overlay_has_region( context->dialog)); button=find_button(root,"Accepter"); g_signal_emit_by_name(button,"clicked"); GtkWidget *second_row=find_ocr_field_row(root,"surname",1); diagnose_ocr_field(root,context->phase,"surname",1,second_row); g_assert_nonnull(second_row); button=g_object_get_data( G_OBJECT(second_row),"identity-modify-button"); g_assert_nonnull(button); GtkWidget *entry=g_object_get_data( G_OBJECT(second_row),"identity-value-entry"); g_assert_nonnull(entry); gtk_editable_set_text(GTK_EDITABLE(entry),"ALICE MODIFIÉE"); g_signal_emit_by_name(button,"clicked"); GtkWidget *third_row=find_ocr_field_row(root,"surname",2); diagnose_ocr_field(root,context->phase,"surname",2,third_row); g_assert_nonnull(third_row); button=g_object_get_data( G_OBJECT(third_row),"identity-reject-button"); g_assert_nonnull(button);g_signal_emit_by_name(button,"clicked"); g_assert_null(find_button(root,"Tout accepter")); g_assert_nonnull(find_button(root,"Acceptée")); g_assert_nonnull(find_button(root,"Modifiée")); g_assert_nonnull(find_button(root,"Rejetée")); g_signal_emit_by_name(find_button(root,"Précédent"),"clicked"); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); g_assert_nonnull(find_button(root,"Acceptée")); guint64 generation=create_person_dialog_test_ocr_generation( context->dialog); button=find_button(root,"Analyser comme document d’identité"); g_signal_emit_by_name(button,"clicked"); g_signal_emit_by_name(button,"clicked"); g_assert_cmpuint(create_person_dialog_test_ocr_generation( context->dialog),==,generation+2); context->phase++;break; case 2: if(find_label(root,"OCR terminé")==NULL)return G_SOURCE_CONTINUE; g_signal_emit_by_name(find_button(root,"Précédent"),"clicked"); g_signal_emit_by_name(find_button(root, "Retirer"),"clicked"); g_assert_false(create_person_dialog_test_overlay_has_region( context->dialog)); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); text=text_view_contents(GTK_TEXT_VIEW(find_named( root,"create-person-ocr-raw-text"))); g_assert_cmpstr(text,==,"");g_free(text); g_signal_emit_by_name(find_button(root,"Précédent"),"clicked"); GtkDropDown *evidence=find_evidence_dropdown(root); g_assert_nonnull(evidence);gtk_drop_down_set_selected(evidence,2); g_signal_emit_by_name(find_button(root, "Ajouter à la sélection"),"clicked"); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); GtkSpinButton *page=find_visible_spin(root);g_assert_nonnull(page); gtk_spin_button_set_value(page,2); g_signal_emit_by_name(find_button(root, "Analyser comme document d’identité"),"clicked"); context->phase++;break; case 3: if(context->confirmation_stage>0){ GtkWidget *scroll=find_named(root, "create-person-confirmation-scroll"); GtkWidget *actions=find_named(root,"create-person-actions"); static const int widths[]={1200,1000,800,760}; static const int heights[]={800,700,600,560}; g_assert_true(GTK_IS_SCROLLED_WINDOW(scroll)); g_assert_true(gtk_widget_get_mapped(scroll)); g_assert_true(gtk_widget_get_mapped(actions)); if(context->confirmation_stage==1){ g_assert_cmpint( gtk_widget_get_width(GTK_WIDGET(context->dialog)), ==,context->confirmation_width); g_assert_cmpint( gtk_widget_get_height(GTK_WIDGET(context->dialog)), ==,context->confirmation_height); GtkAdjustment *adjustment= gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(scroll)); gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment)- gtk_adjustment_get_page_size(adjustment)); g_assert_cmpfloat( gtk_adjustment_get_value(adjustment),>=,0.0); gtk_adjustment_set_value(adjustment, gtk_adjustment_get_lower(adjustment)); g_signal_emit_by_name( find_button(root,"Précédent"),"clicked"); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); g_assert_cmpfloat(gtk_adjustment_get_value(adjustment),==, gtk_adjustment_get_lower(adjustment)); g_assert_cmpint( gtk_widget_get_height(GTK_WIDGET(context->dialog)), ==,context->confirmation_height); }else{ int width=0,height=0; gtk_window_get_default_size( context->dialog,&width,&height); g_assert_cmpint(width,==, widths[context->confirmation_stage-2]); g_assert_cmpint(height,==, heights[context->confirmation_stage-2]); } if(context->confirmation_stage<=4){ guint index=context->confirmation_stage-1; gtk_window_set_default_size(context->dialog, widths[index],heights[index]); context->confirmation_stage++; break; } g_signal_emit_by_name(find_button(root, "Créer la personne"),"clicked"); context->phase++;break; } if(find_label(root,"OCR terminé")==NULL)return G_SOURCE_CONTINUE; { GtkStack *transcription=GTK_STACK(find_named(root, "create-person-ocr-transcription-stack")); GtkTextView *corrected=GTK_TEXT_VIEW(find_named(root, "identity-corrected-transcription")); GtkTextView *raw=GTK_TEXT_VIEW(find_named(root, "create-person-ocr-raw-text")); g_assert_cmpstr(gtk_stack_get_visible_child_name(transcription), ==,"corrected"); g_assert_true(gtk_widget_get_mapped(GTK_WIDGET(corrected))); g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(corrected))); g_assert_true(gtk_text_view_get_editable(corrected)); g_assert_false(gtk_text_view_get_editable(raw)); gtk_text_buffer_set_text(gtk_text_view_get_buffer(corrected), "TRANSCRIPTION PERSONNE SPECIMEN CORRIGÉE",-1); gtk_stack_set_visible_child_name(transcription,"raw"); char *raw_text=text_view_contents(raw); g_assert_nonnull(strstr(raw_text,"NOM : SPECIMEN")); g_free(raw_text); gtk_stack_set_visible_child_name(transcription,"corrected"); char *corrected_text=text_view_contents(corrected); g_assert_cmpstr(corrected_text,==, "TRANSCRIPTION PERSONNE SPECIMEN CORRIGÉE"); g_free(corrected_text); } button=find_button(root,"Accepter"); g_assert_nonnull(button);g_signal_emit_by_name(button,"clicked"); GtkWidget *pdf_second_row=find_ocr_field_row(root,"surname",1); diagnose_ocr_field(root,context->phase,"surname",1,pdf_second_row); g_assert_nonnull(pdf_second_row); button=g_object_get_data( G_OBJECT(pdf_second_row),"identity-modify-button"); g_assert_nonnull(button); GtkWidget *pdf_entry=g_object_get_data( G_OBJECT(pdf_second_row),"identity-value-entry"); g_assert_nonnull(pdf_entry); g_assert_true(gtk_widget_get_sensitive(pdf_entry)); g_assert_true(gtk_editable_get_editable(GTK_EDITABLE(pdf_entry))); char *pdf_raw_value=g_strdup( gtk_editable_get_text(GTK_EDITABLE(pdf_entry))); gtk_editable_set_text(GTK_EDITABLE(pdf_entry),"PAGE 2 PREMIÈRE"); g_signal_emit_by_name(button,"clicked"); g_signal_emit_by_name(g_object_get_data( G_OBJECT(pdf_second_row),"identity-reedit-button"),"clicked"); g_assert_true(gtk_editable_get_editable(GTK_EDITABLE(pdf_entry))); gtk_editable_set_text(GTK_EDITABLE(pdf_entry),"PAGE 2 DEUXIÈME"); g_signal_emit_by_name(button,"clicked"); g_signal_emit_by_name(g_object_get_data( G_OBJECT(pdf_second_row),"identity-restore-button"),"clicked"); g_assert_cmpstr(gtk_editable_get_text(GTK_EDITABLE(pdf_entry)), ==,pdf_raw_value); g_free(pdf_raw_value); gtk_editable_set_text(GTK_EDITABLE(pdf_entry), "PAGE 2 VALEUR FINALE"); g_signal_emit_by_name(button,"clicked"); GtkWidget *manual_input=find_entry(root, "Valeur réellement visible dans la preuve"); gtk_editable_set_text(GTK_EDITABLE(manual_input), "MANUEL SPECIMEN INITIAL"); g_signal_emit_by_name(find_button(root, "Ajouter un champ manquant"),"clicked"); GtkWidget *pdf_manual_row=find_ocr_field_row( root,"document_type",4); g_assert_nonnull(pdf_manual_row); GtkWidget *manual_value=g_object_get_data( G_OBJECT(pdf_manual_row),"identity-value-entry"); g_assert_true(gtk_widget_get_sensitive(manual_value)); g_assert_true(gtk_editable_get_editable(GTK_EDITABLE(manual_value))); gtk_editable_set_text(GTK_EDITABLE(manual_value), "MANUEL SPECIMEN MODIFIÉ"); g_signal_emit_by_name(g_object_get_data( G_OBJECT(pdf_manual_row),"identity-modify-button"),"clicked"); GtkTextView *factual_notes=GTK_TEXT_VIEW(find_named(root, "create-person-ocr-factual-notes")); g_assert_true(gtk_text_view_get_editable(factual_notes)); gtk_text_buffer_set_text(gtk_text_view_get_buffer(factual_notes), "note documentaire SPECIMEN — document incomplet",-1); gtk_window_set_default_size(context->dialog,760,560); g_assert_true(gtk_widget_get_mapped(GTK_WIDGET(factual_notes))); GtkWidget *pdf_third_row=find_ocr_field_row(root,"surname",2); diagnose_ocr_field(root,context->phase,"surname",2,pdf_third_row); g_assert_nonnull(pdf_third_row); button=g_object_get_data( G_OBJECT(pdf_third_row),"identity-reject-button"); g_assert_nonnull(button);g_signal_emit_by_name(button,"clicked"); context->confirmation_width=gtk_widget_get_width( GTK_WIDGET(context->dialog)); context->confirmation_height=gtk_widget_get_height( GTK_WIDGET(context->dialog)); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); g_assert_nonnull(find_label(root, "Appliquer des données OCR à la personne — facultatif")); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); g_assert_nonnull(find_label(root,"Relations factuelles")); g_signal_emit_by_name(find_button(root,"Suivant"),"clicked"); g_assert_nonnull(find_label(root,"Correction OCR enregistrée")); g_assert_nonnull(find_label(root, "Valeurs OCR choisies pour projection")); g_assert_nonnull(find_label(root,"Relations factuelles préparées")); g_assert_nonnull(find_label(root,"page 2")); g_assert_nonnull(find_label(root,"PAGE 2 VALEUR FINALE")); g_assert_nonnull(find_label(root,"authenticité n’est pas établie")); g_assert_null(find_label(root,"brut : PAGE2")); context->confirmation_stage=1;break; case 4: if(context->completion_count!=1)return G_SOURCE_CONTINUE; g_assert_true(context->persisted); g_assert_cmpuint(table_count(context->database_path,"entites"),==,1); g_assert_cmpuint(table_count(context->database_path, "identity_ocr_runs"),==,1); verify_persisted_review(context); g_assert_true(gtk_widget_get_visible(GTK_WIDGET(context->main_window))); GPtrArray *empty=g_ptr_array_new(); Database *database = database_open(context->database_path); g_assert_true(create_person_dialog_present(context->main_window,database,empty, context->root,context->task_manager, tool_registry_find(context->registry,"tesseract"),NULL,completed, context,NULL)); database_close(database); g_ptr_array_unref(empty); GList *windows=gtk_application_get_windows(context->application); context->dialog=windows->data==context->main_window ?GTK_WINDOW(windows->next->data):GTK_WINDOW(windows->data); g_signal_emit_by_name(find_button(GTK_WIDGET(context->dialog), "Annuler"),"clicked"); context->phase++;break; case 5: if(context->completion_count!=2)return G_SOURCE_CONTINUE; g_assert_true(gtk_widget_get_visible(GTK_WIDGET(context->main_window))); context->passed=TRUE; g_application_quit(G_APPLICATION(context->application)); return G_SOURCE_REMOVE; default:break; } return G_SOURCE_CONTINUE; } static void write_png(const char *path) { cairo_surface_t *surface=cairo_image_surface_create( CAIRO_FORMAT_ARGB32,320,160); cairo_t *cr=cairo_create(surface);cairo_set_source_rgb(cr,1,1,1); cairo_paint(cr);cairo_set_source_rgb(cr,0,0,0); cairo_move_to(cr,20,50);cairo_show_text(cr,"SPECIMEN OCR IDENTITE"); g_assert_cmpint(cairo_surface_write_to_png(surface,path),==, CAIRO_STATUS_SUCCESS);cairo_destroy(cr);cairo_surface_destroy(surface); } static void write_pdf(const char *path) { cairo_surface_t *surface=cairo_pdf_surface_create(path,320,160); cairo_t *cr=cairo_create(surface); cairo_move_to(cr,20,50);cairo_show_text(cr,"SPECIMEN PAGE UNE"); cairo_show_page(cr);cairo_move_to(cr,20,50); cairo_show_text(cr,"SPECIMEN PAGE DEUX");cairo_show_page(cr); cairo_destroy(cr);cairo_surface_destroy(surface); } static void activate(GtkApplication *application,gpointer data) { OcrGtkContext *context=data;GError *error=NULL; g_setenv("LABFY_FAKE_IDENTITY_OCR","1",TRUE); char *database_directory=g_build_filename( context->root,"00_BaseDeDonnees",NULL); context->database_path=g_build_filename(database_directory, "Enquete.sqlite",NULL); g_assert_cmpint(g_mkdir_with_parents(database_directory,0700),==,0); g_assert_true(database_initialize(context->database_path, "Enquête SPECIMEN GTK OCR",context->root)); char *evidence_directory=g_build_filename(context->root, "01_Preuves_Originales","identity",NULL); g_assert_cmpint(g_mkdir_with_parents(evidence_directory,0700),==,0); char *png=g_build_filename(evidence_directory,"SPECIMEN.png",NULL); char *pdf=g_build_filename(evidence_directory,"SPECIMEN-page-2.pdf",NULL); write_png(png);write_pdf(pdf); char *png_sha=NULL,*pdf_sha=NULL;guint64 png_size=0,pdf_size=0; g_assert_true(file_hash_compute_sha256(png,NULL,&png_sha,&png_size,&error)); g_assert_true(file_hash_compute_sha256(pdf,NULL,&pdf_sha,&pdf_size,&error)); GPtrArray *records=g_ptr_array_new_with_free_func( (GDestroyNotify)evidence_record_free); EvidenceRecord *record=evidence_record_new( "10000000-0000-4000-8000-000000000001","SPECIMEN.png", "SPECIMEN.png","01_Preuves_Originales/identity/SPECIMEN.png", "document",png_size,png_sha,"2026-07-28T10:00:00Z",NULL,NULL, "SPECIMEN synthétique",EVIDENCE_INTEGRITY_STATUS_VALID,&error); g_assert_nonnull(record);evidence_record_set_display_metadata( record,"Document d’identité",NULL);g_ptr_array_add(records,record); record=evidence_record_new( "10000000-0000-4000-8000-000000000002","SPECIMEN-page-2.pdf", "SPECIMEN-page-2.pdf", "01_Preuves_Originales/identity/SPECIMEN-page-2.pdf", "document",pdf_size,pdf_sha,"2026-07-28T10:00:00Z",NULL,NULL, "PDF SPECIMEN multipage",EVIDENCE_INTEGRITY_STATUS_VALID,&error); g_assert_nonnull(record);evidence_record_set_display_metadata( record,"Document d’identité","application/pdf"); g_ptr_array_add(records,record);g_assert_no_error(error); Database *database=database_open(context->database_path); EvidenceDao *evidence_dao=evidence_dao_new(database,&error); g_assert_no_error(error);g_assert_nonnull(evidence_dao); for(guint index=0;indexlen;index++) g_assert_true(evidence_dao_insert(evidence_dao, g_ptr_array_index(records,index),&error)); g_assert_no_error(error); evidence_dao_free(evidence_dao); context->registry=tool_registry_new(); char *tool=g_canonicalize_filename("tests/fake_document_tool",NULL); g_assert_true(tool_registry_register(context->registry,"tesseract", "Tesseract synthétique",tool,TOOL_REQUIREMENT_OPTIONAL,&error)); g_assert_true(tool_registry_refresh(context->registry,&error)); g_assert_true(tool_registry_set_version(context->registry,"tesseract", "5.0.0 SPECIMEN",&error));g_assert_no_error(error); context->main_window=GTK_WINDOW(gtk_application_window_new(application)); gtk_window_present(context->main_window); g_assert_true(create_person_dialog_present(context->main_window,database,records, context->root,context->task_manager, tool_registry_find(context->registry,"tesseract"),NULL,completed, context,NULL)); database_close(database); GList *windows=gtk_application_get_windows(application); context->dialog=windows->data==context->main_window ?GTK_WINDOW(windows->next->data):GTK_WINDOW(windows->data); g_assert_true(gtk_window_get_transient_for(context->dialog)== context->main_window); g_assert_true(gtk_window_get_modal(context->dialog)); g_assert_true(gtk_window_get_destroy_with_parent(context->dialog)); GtkWidget *designation=find_entry(GTK_WIDGET(context->dialog), "Personne présumée liée aux comptes"); gtk_editable_set_text(GTK_EDITABLE(designation),"PERSONNE SPECIMEN"); g_signal_emit_by_name(find_button(GTK_WIDGET(context->dialog), "Suivant"),"clicked"); GtkWidget *role=find_button(GTK_WIDGET(context->dialog),"Victime"); if(role!=NULL)g_signal_emit_by_name(role,"clicked"); g_signal_emit_by_name(find_button(GTK_WIDGET(context->dialog), "Suivant"),"clicked"); GtkDropDown *evidence=find_evidence_dropdown(GTK_WIDGET(context->dialog)); gtk_drop_down_set_selected(evidence,1); g_signal_emit_by_name(find_button(GTK_WIDGET(context->dialog), "Ajouter à la sélection"),"clicked"); g_signal_emit_by_name(find_button(GTK_WIDGET(context->dialog), "Suivant"),"clicked"); g_timeout_add(10,drive,context); g_ptr_array_unref(records);g_free(database_directory); g_free(evidence_directory);g_free(png);g_free(pdf); g_free(png_sha);g_free(pdf_sha);g_free(tool); } int main(int argc,char **argv) { if(!gtk_init_check()){g_print("SKIP: aucun affichage GTK disponible.\n"); return 0;} OcrGtkContext context={0}; context.root=g_dir_make_tmp("labfy-ocr-gtk-XXXXXX",NULL); context.application=gtk_application_new( "org.labfy.Investigation.OcrDialogTest",G_APPLICATION_NON_UNIQUE); context.task_manager=task_manager_new(); g_signal_connect(context.application,"activate",G_CALLBACK(activate), &context); int status=g_application_run(G_APPLICATION(context.application),argc,argv); g_assert_true(context.passed); tool_registry_free(context.registry);task_manager_free(context.task_manager); g_object_unref(context.application); g_free(context.database_path);remove_tree(context.root);g_free(context.root); return status; }