feat(ui): ajouter l’aperçu avancé et uniformiser les dialogues
This commit is contained in:
parent
dae5f63932
commit
d7131aa1b6
37 changed files with 1784 additions and 378 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -2,6 +2,18 @@
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- OCR d’identité contrôlé et révisable dans la création d’une personne,
|
||||||
|
l’import normal et la fiche de preuve, avec texte brut immuable,
|
||||||
|
transcription corrigée persistante, `manual_override`, `manual_entry` et
|
||||||
|
notes documentaires factuelles.
|
||||||
|
- Consultation complète des données OCR persistées, sélection explicite d’un
|
||||||
|
`OcrRun`, révision sans relance de Tesseract et création d’un nouveau run
|
||||||
|
lors d’une nouvelle analyse.
|
||||||
|
- Aperçu partagé avec zoom, ajustement, défilement bidirectionnel, navigation
|
||||||
|
PDF multipage, compteur de pages et provenance OCR synchronisée.
|
||||||
|
- Politique commune des dialogues GTK métiers complexes : parent transitoire,
|
||||||
|
géométrie responsive, répartition initiale 2/3–1/3, formulaire défilable et
|
||||||
|
barre d’actions fixe.
|
||||||
- Préparation du pivot e-mail : pipeline EML asynchrone, extraction MIME
|
- Préparation du pivot e-mail : pipeline EML asynchrone, extraction MIME
|
||||||
sécurisée, propositions bancaires IBAN/BIC et vocabulaire contrôlé.
|
sécurisée, propositions bancaires IBAN/BIC et vocabulaire contrôlé.
|
||||||
|
|
||||||
|
|
@ -23,6 +35,12 @@
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Rafraîchissement immédiat de Workspace après import ou révision OCR et
|
||||||
|
persistance des données sur l’UUID définitif de la preuve, y compris après
|
||||||
|
fermeture et réouverture de SQLite.
|
||||||
|
- Fermeture sûre du dialogue après enregistrement d’une révision OCR, avec
|
||||||
|
protection contre le double clic, maintien ouvert en cas d’échec et absence
|
||||||
|
de relance de Tesseract.
|
||||||
- Conservation du cadrage du graphe pendant les rechargements.
|
- Conservation du cadrage du graphe pendant les rechargements.
|
||||||
- Persistance SQLite du zoom et de la position du graphe entre deux sessions.
|
- Persistance SQLite du zoom et de la position du graphe entre deux sessions.
|
||||||
- Réduction de la fenêtre d’intégration des extractions pour maintenir les
|
- Réduction de la fenêtre d’intégration des extractions pour maintenir les
|
||||||
|
|
|
||||||
17
Makefile
17
Makefile
|
|
@ -164,6 +164,7 @@ TEST_OCR_PROVENANCE_OVERLAY_GTK := tests/test_ocr_provenance_overlay_gtk
|
||||||
TEST_EVIDENCE_METADATA_DIALOG_GTK := tests/test_evidence_metadata_dialog_gtk
|
TEST_EVIDENCE_METADATA_DIALOG_GTK := tests/test_evidence_metadata_dialog_gtk
|
||||||
TEST_EVIDENCE_IDENTITY_IMPORT_GTK := tests/test_evidence_identity_import_gtk
|
TEST_EVIDENCE_IDENTITY_IMPORT_GTK := tests/test_evidence_identity_import_gtk
|
||||||
TEST_WORKSPACE_IDENTITY_OCR_GTK := tests/test_workspace_identity_ocr_gtk
|
TEST_WORKSPACE_IDENTITY_OCR_GTK := tests/test_workspace_identity_ocr_gtk
|
||||||
|
TEST_DIALOG_GEOMETRY_GTK := tests/test_dialog_geometry_gtk
|
||||||
FAKE_DOCUMENT_TOOL := tests/fake_document_tool
|
FAKE_DOCUMENT_TOOL := tests/fake_document_tool
|
||||||
|
|
||||||
DOCUMENT_ANALYSIS_TEST_SOURCES := \
|
DOCUMENT_ANALYSIS_TEST_SOURCES := \
|
||||||
|
|
@ -178,6 +179,10 @@ $(TEST_DOCUMENT_TOOL_RUNNER): tests/test_document_tool_runner.c \
|
||||||
tests/test_document_tool_runner.c \
|
tests/test_document_tool_runner.c \
|
||||||
$(DOCUMENT_ANALYSIS_TEST_SOURCES) -o $@ $(TEST_LDFLAGS)
|
$(DOCUMENT_ANALYSIS_TEST_SOURCES) -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
|
$(TEST_DIALOG_GEOMETRY_GTK): tests/test_dialog_geometry_gtk.c \
|
||||||
|
src/views/dialog_geometry.c
|
||||||
|
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
$(TEST_IDENTITY_OCR): tests/test_identity_ocr.c \
|
$(TEST_IDENTITY_OCR): tests/test_identity_ocr.c \
|
||||||
|
|
@ -571,6 +576,7 @@ $(TEST_EVIDENCE_TYPE_DAO): \
|
||||||
$(TEST_EVIDENCE_IMPORT_DIALOG): \
|
$(TEST_EVIDENCE_IMPORT_DIALOG): \
|
||||||
tests/test_evidence_import_dialog.c \
|
tests/test_evidence_import_dialog.c \
|
||||||
src/views/evidence_import_dialog.c \
|
src/views/evidence_import_dialog.c \
|
||||||
|
src/views/dialog_geometry.c \
|
||||||
src/models/evidence_type.c \
|
src/models/evidence_type.c \
|
||||||
src/widgets/evidence_preview_widget.c \
|
src/widgets/evidence_preview_widget.c \
|
||||||
src/core/evidence_preview_task.c src/core/evidence_preview.c \
|
src/core/evidence_preview_task.c src/core/evidence_preview.c \
|
||||||
|
|
@ -883,7 +889,8 @@ $(TEST_PERSON_DIALOG_LIFECYCLE): tests/test_person_dialog_lifecycle.c \
|
||||||
$(CC) $(TEST_CFLAGS) -Wpedantic $^ -o $@ $(TEST_LDFLAGS)
|
$(CC) $(TEST_CFLAGS) -Wpedantic $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
$(TEST_CREATE_PERSON_DIALOG_GTK): tests/test_create_person_dialog_gtk.c \
|
$(TEST_CREATE_PERSON_DIALOG_GTK): tests/test_create_person_dialog_gtk.c \
|
||||||
src/views/create_person_dialog.c src/core/person_dialog_lifecycle.c \
|
src/views/create_person_dialog.c src/views/dialog_geometry.c \
|
||||||
|
src/core/person_dialog_lifecycle.c \
|
||||||
src/core/person_confirmation_summary.c \
|
src/core/person_confirmation_summary.c \
|
||||||
src/core/evidence_staging.c src/core/evidence_staging_task.c \
|
src/core/evidence_staging.c src/core/evidence_staging_task.c \
|
||||||
src/core/evidence_preview_task.c src/core/evidence_preview.c \
|
src/core/evidence_preview_task.c src/core/evidence_preview.c \
|
||||||
|
|
@ -906,7 +913,8 @@ $(TEST_CREATE_PERSON_DIALOG_GTK): tests/test_create_person_dialog_gtk.c \
|
||||||
|
|
||||||
$(TEST_CREATE_PERSON_DIALOG_OCR_GTK): \
|
$(TEST_CREATE_PERSON_DIALOG_OCR_GTK): \
|
||||||
tests/test_create_person_dialog_ocr_gtk.c \
|
tests/test_create_person_dialog_ocr_gtk.c \
|
||||||
src/views/create_person_dialog.c src/core/person_dialog_lifecycle.c \
|
src/views/create_person_dialog.c src/views/dialog_geometry.c \
|
||||||
|
src/core/person_dialog_lifecycle.c \
|
||||||
src/core/person_confirmation_summary.c \
|
src/core/person_confirmation_summary.c \
|
||||||
src/core/evidence_staging.c src/core/evidence_staging_task.c \
|
src/core/evidence_staging.c src/core/evidence_staging_task.c \
|
||||||
src/core/evidence_preview_task.c src/core/evidence_preview.c \
|
src/core/evidence_preview_task.c src/core/evidence_preview.c \
|
||||||
|
|
@ -1122,7 +1130,8 @@ test: \
|
||||||
$(TEST_OCR_PROVENANCE_OVERLAY_GTK) \
|
$(TEST_OCR_PROVENANCE_OVERLAY_GTK) \
|
||||||
$(TEST_EVIDENCE_METADATA_DIALOG_GTK) \
|
$(TEST_EVIDENCE_METADATA_DIALOG_GTK) \
|
||||||
$(TEST_EVIDENCE_IDENTITY_IMPORT_GTK) \
|
$(TEST_EVIDENCE_IDENTITY_IMPORT_GTK) \
|
||||||
$(TEST_WORKSPACE_IDENTITY_OCR_GTK)
|
$(TEST_WORKSPACE_IDENTITY_OCR_GTK) \
|
||||||
|
$(TEST_DIALOG_GEOMETRY_GTK)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -1216,6 +1225,7 @@ test: \
|
||||||
@$(TEST_EVIDENCE_METADATA_DIALOG_GTK)
|
@$(TEST_EVIDENCE_METADATA_DIALOG_GTK)
|
||||||
@$(TEST_EVIDENCE_IDENTITY_IMPORT_GTK)
|
@$(TEST_EVIDENCE_IDENTITY_IMPORT_GTK)
|
||||||
@$(TEST_WORKSPACE_IDENTITY_OCR_GTK)
|
@$(TEST_WORKSPACE_IDENTITY_OCR_GTK)
|
||||||
|
@$(TEST_DIALOG_GEOMETRY_GTK)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -1313,6 +1323,7 @@ clean:
|
||||||
$(TEST_EVIDENCE_METADATA_DIALOG_GTK) \
|
$(TEST_EVIDENCE_METADATA_DIALOG_GTK) \
|
||||||
$(TEST_EVIDENCE_IDENTITY_IMPORT_GTK) \
|
$(TEST_EVIDENCE_IDENTITY_IMPORT_GTK) \
|
||||||
$(TEST_WORKSPACE_IDENTITY_OCR_GTK) \
|
$(TEST_WORKSPACE_IDENTITY_OCR_GTK) \
|
||||||
|
$(TEST_DIALOG_GEOMETRY_GTK) \
|
||||||
$(FAKE_DOCUMENT_TOOL)
|
$(FAKE_DOCUMENT_TOOL)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
65
README.md
65
README.md
|
|
@ -123,20 +123,30 @@ Les outils externes sont lancés avec `GSubprocess` et des arguments séparés.
|
||||||
|
|
||||||
## Fonctionnalités déjà présentes
|
## Fonctionnalités déjà présentes
|
||||||
|
|
||||||
La création d’une personne propose une étape facultative d’OCR contrôlé des
|
La création d’une personne et l’import normal d’une preuve proposent un OCR
|
||||||
documents d’identité. Elle ne démarre que sur action explicite, travaille sur
|
contrôlé des documents d’identité. Il ne démarre que sur action explicite,
|
||||||
une copie vérifiée PNG, JPEG, HEIC, HEIF ou sur une page PDF choisie, et laisse
|
travaille sur une copie vérifiée PNG, JPEG, HEIC, HEIF ou sur une page PDF
|
||||||
chaque champ à l’état « À vérifier ». L’acceptation, la modification ou le rejet
|
choisie, et conserve séparément le texte OCR brut immuable et une transcription
|
||||||
s’effectuent champ par champ, sans verdict d’authenticité, reconnaissance
|
corrigée rééditable et réinitialisable. Les propositions restent révisables :
|
||||||
faciale, fusion de personne ni changement automatique du statut
|
elles peuvent être acceptées, corrigées à nouveau, restaurées depuis leur
|
||||||
d’identification.
|
valeur brute ou rejetées. Un champ visible mais omis par l’OCR peut être saisi
|
||||||
|
avec l’origine `manual_entry` ; une correction d’une valeur extraite conserve
|
||||||
|
l’origine `manual_override`. Des notes factuelles peuvent signaler un document
|
||||||
|
tronqué, flou, masqué ou incomplet, sans reconstruire une zone absente.
|
||||||
|
|
||||||
Ce parcours est également disponible lors de l’import normal d’une preuve,
|
La fiche directe d’une preuve relit depuis SQLite le texte brut, la
|
||||||
avec sélection explicite ou présélection de la personne. Il traite une seule
|
transcription corrigée, les exécutions `OcrRun`, observations, champs, notes,
|
||||||
preuve par opération. L’import multiple sans OCR reste disponible ; plusieurs
|
artefacts, empreintes SHA-256, personne liée et provenance graphique. Un run
|
||||||
preuves doivent être OCRisées successivement, chacune dans son propre
|
est sélectionné explicitement lorsqu’un historique en contient plusieurs.
|
||||||
parcours. Les dialogues concernés affichent la liste à gauche et un grand
|
« Réviser l’analyse OCR » modifie uniquement ce run sans relancer Tesseract ;
|
||||||
aperçu redimensionnable à droite.
|
« Relancer une nouvelle analyse » crée un nouveau run sans écraser
|
||||||
|
l’historique.
|
||||||
|
|
||||||
|
L’import multiple sans OCR reste disponible. L’OCR groupé n’est pas pris en
|
||||||
|
charge : les preuves importées ensemble sont ensuite analysées une par une
|
||||||
|
depuis leur fiche, sur leur UUID définitif et sans doublon. Aucun de ces
|
||||||
|
parcours ne produit de verdict d’authenticité, de reconnaissance faciale, de
|
||||||
|
fusion de personne, d’identité certaine ou de rôle d’auteur automatique.
|
||||||
|
|
||||||
Le socle actuel comprend notamment :
|
Le socle actuel comprend notamment :
|
||||||
|
|
||||||
|
|
@ -212,8 +222,18 @@ Le socle actuel comprend notamment :
|
||||||
définitifs qu’après confirmation globale ;
|
définitifs qu’après confirmation globale ;
|
||||||
- rattachement transactionnel de toutes les preuves retenues, avec rollback
|
- rattachement transactionnel de toutes les preuves retenues, avec rollback
|
||||||
SQLite et suppression compensatoire des copies définitives en cas d’échec.
|
SQLite et suppression compensatoire des copies définitives en cas d’échec.
|
||||||
- aperçu asynchrone contrôlé PNG/JPEG, HEIC/HEIF, MP4/MOV, texte, EML et
|
- aperçu asynchrone contrôlé PNG/JPEG, HEIC/HEIF, MP4/MOV, texte, EML et PDF
|
||||||
première page PDF, sans OCR ni écriture SQLite ;
|
multipage, avec zoom de 25 à 400 %, ajustement, défilements horizontal et
|
||||||
|
vertical, navigation et compteur de pages ; la provenance OCR reste alignée
|
||||||
|
avec la page, le zoom et le défilement ;
|
||||||
|
|
||||||
|
Les dialogues métiers complexes suivent une politique GTK commune : parent
|
||||||
|
réel via `transient_for`, modalité adaptée et présentation sans coordonnées
|
||||||
|
absolues sous Wayland. Ils visent 1200 × 800, avec un minimum utile de
|
||||||
|
800 × 600 lorsque la zone de travail le permet, un formulaire défilable à
|
||||||
|
gauche sur environ deux tiers, un aperçu redimensionnable à droite et une
|
||||||
|
barre d’actions fixe. Les alertes simples, popups `GtkDropDown` et sélecteurs
|
||||||
|
de fichiers natifs ne sont pas concernés.
|
||||||
|
|
||||||
### Pivot EML
|
### Pivot EML
|
||||||
|
|
||||||
|
|
@ -390,6 +410,21 @@ Les nouveaux modules doivent être accompagnés de tests couvrant :
|
||||||
- les responsabilités mémoire ;
|
- les responsabilités mémoire ;
|
||||||
- les régressions possibles.
|
- les régressions possibles.
|
||||||
|
|
||||||
|
Les parcours GTK OCR et aperçu doivent aussi être exécutés séparément sur un
|
||||||
|
poste avec affichage :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_create_person_dialog_ocr_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_identity_import_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_workspace_identity_ocr_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_preview_widget_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_dialog_geometry_gtk
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests emploient uniquement des documents `SPECIMEN`, des fichiers
|
||||||
|
temporaires et des bases SQLite temporaires fermées puis rouvertes. Un `SKIP`
|
||||||
|
lié à l’absence d’affichage ne remplace pas cette validation GTK réelle.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Développement
|
## Développement
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,36 @@
|
||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
`EvidencePreviewWidget` est l’adaptateur GTK unique de `EvidencePreview` pour
|
`EvidencePreviewWidget` est l’adaptateur GTK partagé de `EvidencePreview` pour
|
||||||
`CreatePersonDialog` et la fiche directe. Il encapsule les états, le rendu
|
`CreatePersonDialog`, l’import, la révision OCR et la fiche directe. Il
|
||||||
EML/PDF/image/texte/vidéo, l’annulation, la génération, la garde de session et
|
encapsule les états, le rendu EML/PDF/image/texte/vidéo, l’annulation, la
|
||||||
l’arrêt du média. Les deux écrans lui transmettent uniquement une source
|
génération, la garde de session et l’arrêt du média. Les écrans lui
|
||||||
interne ou de staging accompagnée du SHA-256 attendu.
|
transmettent uniquement une source interne ou de staging accompagnée du
|
||||||
|
SHA-256 attendu.
|
||||||
|
|
||||||
## Aperçu contrôlé multi-format
|
## Aperçu contrôlé multi-format
|
||||||
|
|
||||||
`EvidencePreview` vérifie l’empreinte du fichier interne ou de staging,
|
`EvidencePreview` vérifie l’empreinte du fichier interne ou de staging,
|
||||||
détecte son contenu et produit un résultat borné sans GTK. `BackgroundTask`
|
détecte son contenu et produit un résultat borné sans GTK. `BackgroundTask`
|
||||||
travaille hors du thread principal ; `CreatePersonDialog` y crée ensuite
|
travaille hors du thread principal ; le widget crée ensuite textures, buffers
|
||||||
textures, buffers et médias, rejette les générations périmées et arrête tout
|
et médias sur le contexte GTK, rejette les générations périmées et arrête tout
|
||||||
média au changement ou à la fermeture. L’aperçu ne persiste rien.
|
média au changement ou à la fermeture. L’aperçu ne persiste rien.
|
||||||
|
|
||||||
|
Pour les images et PDF, la barre compacte fournit zoom avant, zoom arrière et
|
||||||
|
retour à l’ajustement. Une image agrandie reste défilable horizontalement et
|
||||||
|
verticalement. Les PDF multipages disposent des actions page précédente et
|
||||||
|
suivante et d’un compteur `Page X / N` ; le changement de page conserve le
|
||||||
|
zoom. `OcrProvenanceOverlay` applique le même zoom, le même défilement et la
|
||||||
|
même page afin que la provenance reste alignée.
|
||||||
|
|
||||||
Le mode aperçu de `EmlMimeExtractor` réutilise le parcours récursif et les
|
Le mode aperçu de `EmlMimeExtractor` réutilise le parcours récursif et les
|
||||||
décodages RFC 2047/2231 sans écrire les pièces jointes. Il préfère
|
décodages RFC 2047/2231 sans écrire les pièces jointes. Il préfère
|
||||||
`text/plain`, transforme un éventuel HTML en texte inerte et retourne
|
`text/plain`, transforme un éventuel HTML en texte inerte et retourne
|
||||||
l’inventaire possédé. Le contrôleur vidéo indépendant de GTK orchestre
|
l’inventaire possédé. Le contrôleur vidéo indépendant de GTK orchestre
|
||||||
pause, retour au début, détachement et libération via des actions injectées.
|
pause, retour au début, détachement et libération via des actions injectées.
|
||||||
|
|
||||||
> **Version :** 3.1
|
> **Version :** 3.2
|
||||||
> **Dernière mise à jour :** 2026-07-28
|
> **Dernière mise à jour :** 2026-07-30
|
||||||
> **Schéma SQLite courant :** V14
|
> **Schéma SQLite courant :** V17
|
||||||
|
|
||||||
## Personnes contextuelles — SQLite V14
|
## Personnes contextuelles — SQLite V14
|
||||||
|
|
||||||
|
|
@ -33,32 +41,64 @@ confiance et notes. Les anciens codes sont copiés littéralement depuis
|
||||||
les rôles et le rattachement manuel dans une transaction unique.
|
les rôles et le rattachement manuel dans une transaction unique.
|
||||||
|
|
||||||
Le cœur `EvidencePreview` vérifie l’intégrité avant décodage PNG/JPEG, applique
|
Le cœur `EvidencePreview` vérifie l’intégrité avant décodage PNG/JPEG, applique
|
||||||
des bornes et ne renvoie qu’un PNG mémoire réduit. Une `BackgroundTask`
|
des bornes et ne renvoie qu’un rendu mémoire réduit. Une `BackgroundTask`
|
||||||
travaille hors du thread GTK ; la texture est créée sur le contexte principal.
|
travaille hors du thread GTK ; la texture est créée sur le contexte principal.
|
||||||
Le dialogue annule l’ancienne tâche et rejette les générations obsolètes.
|
Le dialogue annule l’ancienne tâche et rejette les générations obsolètes.
|
||||||
L’interface conserve une génération de session et les chemins stables du
|
L’interface conserve une génération de session et les chemins stables du
|
||||||
projet et de sa base. PDF, vidéo, EML avancé et OCR restent exclus.
|
projet et de sa base.
|
||||||
|
|
||||||
## OCR contrôlé d’identité — SQLite V15
|
## OCR contrôlé d’identité — SQLite V15 à V17
|
||||||
|
|
||||||
La V15 sépare l’exécution OCR, l’observation du document et les observations
|
`IdentityOcrWorkflow` est l’unique orchestration du moteur OCR, réutilisée par
|
||||||
de champs. Le moteur reçoit uniquement une copie contrôlée ou une page PDF
|
`CreatePersonDialog`, `EvidenceIdentityOcrDialog`, l’import normal et
|
||||||
explicitement choisie, conserve texte brut, TSV, paramètres, langues, version,
|
Workspace. La V15 sépare l’exécution OCR, l’observation du document et les
|
||||||
SHA-256, confiance et coordonnées, puis laisse la révision à l’utilisateur.
|
observations de champs. La V16 ajoute notamment l’origine `manual_entry` pour
|
||||||
Les widgets n’accèdent pas à SQLite : le coordinateur persiste les décisions
|
un champ visible mais omis par l’OCR. La V17 ajoute une transcription corrigée
|
||||||
acceptées ou modifiées dans la transaction finale et compense les fichiers en
|
humaine distincte du texte OCR brut.
|
||||||
cas d’échec.
|
|
||||||
|
Le moteur reçoit uniquement une copie contrôlée ou une page PDF explicitement
|
||||||
|
choisie, conserve texte brut, TSV, paramètres, langues, version, SHA-256,
|
||||||
|
confiance et coordonnées, puis laisse la révision à l’utilisateur. Le texte
|
||||||
|
brut n’est jamais remplacé. Une correction de valeur garde
|
||||||
|
`manual_override`; une saisie sans valeur OCR garde `manual_entry`. Les notes
|
||||||
|
restent factuelles et ne reconstruisent jamais une partie absente d’un
|
||||||
|
document tronqué.
|
||||||
|
|
||||||
|
Les widgets n’accèdent pas à SQLite. Le coordinateur persiste sur l’UUID
|
||||||
|
définitif de la preuve dans une transaction unique, avec rollback,
|
||||||
|
compensation des fichiers et garde de session juste avant commit. L’import
|
||||||
|
multiple reste sans OCR groupé : chaque preuve est analysée ensuite,
|
||||||
|
individuellement et sans réimport ni doublon.
|
||||||
|
|
||||||
La provenance visuelle est rendue par `OcrProvenanceOverlay`. La conversion
|
La provenance visuelle est rendue par `OcrProvenanceOverlay`. La conversion
|
||||||
des coordonnées source vers la zone affichée est indépendante de GTK et tient
|
des coordonnées source vers la zone affichée est indépendante de GTK et tient
|
||||||
compte du ratio, de la réduction, de l’agrandissement et des marges. Le
|
compte du ratio, de la réduction, de l’agrandissement et des marges. Le
|
||||||
rectangle est transitoire : aucune annotation n’est écrite dans la preuve.
|
rectangle est transitoire : aucune annotation n’est écrite dans la preuve.
|
||||||
Les langues proposées proviennent exclusivement de `tesseract --list-langs`.
|
Les langues proposées proviennent exclusivement de `tesseract --list-langs`.
|
||||||
Le DAO V15 expose des lectures possédées pour les exécutions, observations de
|
Les DAO structurés exposent des lectures possédées pour les runs, observations
|
||||||
documents et observations de champs, ainsi que des listes ordonnées par preuve,
|
de documents et de champs, notes et artefacts. La fiche de preuve recharge ces
|
||||||
personne ou observation documentaire.
|
données depuis SQLite, sélectionne explicitement un run dans un modèle
|
||||||
|
`GtkDropDown` stable et affiche texte brut, transcription corrigée, personne
|
||||||
|
liée, SHA-256 et provenance graphique. Réviser conserve l’UUID du run et ne
|
||||||
|
relance pas Tesseract ; relancer crée un nouveau run sans écraser
|
||||||
|
l’historique.
|
||||||
> **Statut :** architecture courante
|
> **Statut :** architecture courante
|
||||||
|
|
||||||
|
## Politique des dialogues GTK métiers
|
||||||
|
|
||||||
|
`labfy_dialog_prepare()` et `labfy_dialog_present()` centralisent la séquence
|
||||||
|
des dialogues complexes : `transient_for` vers la vraie fenêtre parente,
|
||||||
|
modalité appropriée, géométrie commune, puis `gtk_window_present()`. Aucune
|
||||||
|
coordonnée absolue n’est utilisée sous GTK4/Wayland.
|
||||||
|
|
||||||
|
La cible initiale est proche de 1200 × 800, avec un minimum utile de
|
||||||
|
800 × 600 lorsque la zone de travail le permet. Un `GtkPaned` place
|
||||||
|
initialement le formulaire défilable à gauche sur environ deux tiers et
|
||||||
|
l’aperçu à droite sur un tiers ; sa position n’est appliquée qu’après la
|
||||||
|
première allocation réelle puis reste entièrement modifiable. Les actions
|
||||||
|
restent fixes en bas. Cette politique exclut les alertes simples, les popups
|
||||||
|
`GtkDropDown` et les sélecteurs de fichiers natifs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Objectif
|
## 1. Objectif
|
||||||
|
|
@ -355,7 +395,7 @@ validation du chemin
|
||||||
↓
|
↓
|
||||||
création de l'arborescence
|
création de l'arborescence
|
||||||
↓
|
↓
|
||||||
initialisation transactionnelle de SQLite V14
|
initialisation transactionnelle de SQLite V17
|
||||||
↓
|
↓
|
||||||
création de l'identité de l'enquête
|
création de l'identité de l'enquête
|
||||||
↓
|
↓
|
||||||
|
|
@ -460,9 +500,9 @@ sont supprimées après rollback. `PersonCreationTask` exécute cette orchestrat
|
||||||
hors du thread GTK. Un changement de session annule les tâches et interdit le
|
hors du thread GTK. Un changement de session annule les tâches et interdit le
|
||||||
rafraîchissement d’une autre enquête.
|
rafraîchissement d’une autre enquête.
|
||||||
|
|
||||||
Les aperçus PNG/JPEG utilisent exclusivement le fichier interne d’une preuve
|
Les aperçus utilisent exclusivement le fichier interne d’une preuve existante
|
||||||
existante ou sa copie de staging. PDF, vidéo, EML et autres formats ne sont
|
ou sa copie de staging. L’OCR d’identité n’est lancé que sur demande explicite
|
||||||
pas analysés dans cette tranche.
|
et sur un format compatible contrôlé.
|
||||||
|
|
||||||
Les tâches longues utilisent l'infrastructure de tâche d'arrière-plan et le
|
Les tâches longues utilisent l'infrastructure de tâche d'arrière-plan et le
|
||||||
gestionnaire de tâches.
|
gestionnaire de tâches.
|
||||||
|
|
@ -791,6 +831,14 @@ Les scénarios critiques incluent :
|
||||||
- conservation de la valeur brute ;
|
- conservation de la valeur brute ;
|
||||||
- absence d'une dépendance optionnelle.
|
- absence d'une dépendance optionnelle.
|
||||||
|
|
||||||
|
Les parcours OCR ferment puis rouvrent leurs bases SQLite temporaires afin de
|
||||||
|
vérifier la persistance du texte brut, de la transcription corrigée, des
|
||||||
|
champs et de l’historique multi-run. Les tests GTK réels passent par
|
||||||
|
`MainWindow`, `Workspace` et les contrôles de production, avec des fixtures
|
||||||
|
`SPECIMEN`, `G_DEBUG=fatal-criticals` et un timeout. Ils couvrent aussi
|
||||||
|
l’aperçu PNG/JPEG/PDF multipage, la provenance, la géométrie, l’annulation,
|
||||||
|
les résultats tardifs, les erreurs de transaction et les cycles de vie.
|
||||||
|
|
||||||
Validation :
|
Validation :
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
# Guide de développement
|
# Guide de développement
|
||||||
|
|
||||||
Pour la tranche OCR identité, les tests ciblés sont
|
Pour l’OCR d’identité, les tests ciblés sont `tests/test_identity_ocr`,
|
||||||
`tests/test_identity_ocr`, `tests/test_identity_ocr_preprocessor`,
|
`tests/test_identity_ocr_preprocessor`,
|
||||||
`tests/test_person_creation_coordinator`,
|
`tests/test_person_creation_coordinator`,
|
||||||
`tests/test_ocr_provenance_overlay_gtk` et
|
`tests/test_create_person_dialog_ocr_gtk`,
|
||||||
`tests/test_create_person_dialog_gtk`. Le test Tesseract réel est facultatif
|
`tests/test_evidence_identity_import_gtk`,
|
||||||
|
`tests/test_evidence_metadata_dialog_gtk`,
|
||||||
|
`tests/test_workspace_identity_ocr_gtk` et
|
||||||
|
`tests/test_ocr_provenance_overlay_gtk`. Le test Tesseract réel est facultatif
|
||||||
et s’ignore explicitement si l’outil ou une langue compatible manque.
|
et s’ignore explicitement si l’outil ou une langue compatible manque.
|
||||||
Les fixtures directes du préprocesseur sont générées en mémoire ou dans un
|
Les fixtures directes du préprocesseur sont générées en mémoire ou dans un
|
||||||
répertoire temporaire : JPEG avec APP1 EXIF, HEIC/HEIF via libheif et PDF
|
répertoire temporaire : JPEG avec APP1 EXIF, HEIC/HEIF via libheif et PDF
|
||||||
multipage via Cairo. Elles ne doivent jamais être remplacées par un document
|
multipage via Cairo. Elles ne doivent jamais être remplacées par un document
|
||||||
réel.
|
réel.
|
||||||
|
|
||||||
La validation ciblée du composant partagé comprend
|
La validation ciblée de l’aperçu partagé comprend
|
||||||
`test_evidence_preview_widget_gtk`, `test_evidence_preview`,
|
`test_evidence_preview_widget_gtk`, `test_evidence_preview`,
|
||||||
`test_evidence_video_preview_controller` et
|
`test_evidence_video_preview_controller` et
|
||||||
`test_create_person_dialog_gtk`, ce dernier sous
|
`test_create_person_dialog_gtk`, ce dernier sous
|
||||||
|
|
@ -27,8 +30,8 @@ conteneur synthétique. Les transformations d’orientation libheif restent
|
||||||
appliquées par le décodage standard ; seule l’image principale est rendue et
|
appliquées par le décodage standard ; seule l’image principale est rendue et
|
||||||
le nombre d’images de premier niveau est exposé.
|
le nombre d’images de premier niveau est exposé.
|
||||||
|
|
||||||
> **Version :** 2.1
|
> **Version :** 2.2
|
||||||
> **Dernière mise à jour :** 2026-07-28
|
> **Dernière mise à jour :** 2026-07-30
|
||||||
> **Projet :** Labfy Investigation
|
> **Projet :** Labfy Investigation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -282,6 +285,30 @@ make -j8 \
|
||||||
Ces tests utilisent uniquement des fichiers `SPECIMEN`, des répertoires
|
Ces tests utilisent uniquement des fichiers `SPECIMEN`, des répertoires
|
||||||
temporaires et des bases SQLite temporaires.
|
temporaires et des bases SQLite temporaires.
|
||||||
|
|
||||||
|
Tests GTK réels de l’OCR, de l’aperçu et de la géométrie :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -j8 \
|
||||||
|
tests/test_create_person_dialog_ocr_gtk \
|
||||||
|
tests/test_evidence_identity_import_gtk \
|
||||||
|
tests/test_evidence_metadata_dialog_gtk \
|
||||||
|
tests/test_workspace_identity_ocr_gtk \
|
||||||
|
tests/test_evidence_preview_widget_gtk \
|
||||||
|
tests/test_dialog_geometry_gtk
|
||||||
|
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_create_person_dialog_ocr_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_identity_import_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_metadata_dialog_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_workspace_identity_ocr_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_preview_widget_gtk
|
||||||
|
G_DEBUG=fatal-criticals timeout 30s ./tests/test_dialog_geometry_gtk
|
||||||
|
```
|
||||||
|
|
||||||
|
Ils doivent cliquer sur les contrôles de production, passer par `MainWindow`
|
||||||
|
et `Workspace` lorsque le scénario le demande, puis fermer et rouvrir la base
|
||||||
|
SQLite temporaire. Un environnement sans affichage peut produire un `SKIP`,
|
||||||
|
mais celui-ci ne remplace pas l’exécution séparée sur le poste GTK réel.
|
||||||
|
|
||||||
La fixture manuelle est
|
La fixture manuelle est
|
||||||
`tests/fixtures/eml/manual_smoke_test.eml`. Elle est exclusivement
|
`tests/fixtures/eml/manual_smoke_test.eml`. Elle est exclusivement
|
||||||
synthétique. Pour la validation GTK, créer une enquête neuve dans un
|
synthétique. Pour la validation GTK, créer une enquête neuve dans un
|
||||||
|
|
@ -541,6 +568,15 @@ Un dialogue :
|
||||||
- ne réalise pas une opération longue directement ;
|
- ne réalise pas une opération longue directement ;
|
||||||
- présente un résumé avant une écriture importante.
|
- présente un résumé avant une écriture importante.
|
||||||
|
|
||||||
|
Les dialogues métiers complexes utilisent `labfy_dialog_prepare()` puis
|
||||||
|
`labfy_dialog_present()`. La vraie fenêtre parente est définie avec
|
||||||
|
`transient_for`; aucune coordonnée absolue n’est utilisée. Ils visent
|
||||||
|
1200 × 800 et un minimum utile de 800 × 600, avec formulaire défilable à
|
||||||
|
gauche sur environ deux tiers, aperçu à droite et barre d’actions fixe. Le
|
||||||
|
séparateur reste déplaçable et n’est pas réinitialisé après sa première
|
||||||
|
allocation. Cette règle ne concerne pas les alertes, popups `GtkDropDown` ou
|
||||||
|
sélecteurs natifs.
|
||||||
|
|
||||||
### 11.3 Messages utilisateur
|
### 11.3 Messages utilisateur
|
||||||
|
|
||||||
Les détails techniques restent dans les journaux.
|
Les détails techniques restent dans les journaux.
|
||||||
|
|
@ -663,3 +699,24 @@ Avant de considérer une modification terminée :
|
||||||
- [ ] `make -j8 test` passe ;
|
- [ ] `make -j8 test` passe ;
|
||||||
- [ ] `git diff --check` passe ;
|
- [ ] `git diff --check` passe ;
|
||||||
- [ ] la documentation est à jour.
|
- [ ] la documentation est à jour.
|
||||||
|
|
||||||
|
## 16. Validation manuelle du ticket #109
|
||||||
|
|
||||||
|
Utiliser exclusivement une enquête, une base SQLite et des documents
|
||||||
|
`SPECIMEN` temporaires. Vérifier au minimum :
|
||||||
|
|
||||||
|
1. création d’une personne avec OCR, correction de transcription, réédition
|
||||||
|
d’un champ, `manual_entry`, notes et confirmation ;
|
||||||
|
2. visibilité immédiate de la preuve dans Workspace, puis fermeture et
|
||||||
|
réouverture de SQLite ;
|
||||||
|
3. import multiple sans OCR, puis analyse individuelle de chaque preuve depuis
|
||||||
|
sa fiche, sans doublon ;
|
||||||
|
4. sélection de plusieurs `OcrRun`, révision du seul run choisi sans
|
||||||
|
Tesseract, puis nouvelle analyse créant un run supplémentaire ;
|
||||||
|
5. aperçu PNG, JPEG et PDF `SPECIMEN` multipage : zoom, ajustement,
|
||||||
|
défilements, navigation, compteur et provenance ;
|
||||||
|
6. dialogues à 1200 × 800 et 760 × 560 : parent transitoire, formulaire
|
||||||
|
défilable, répartition 2/3–1/3 et actions toujours visibles.
|
||||||
|
|
||||||
|
La validation manuelle réussie confirme ces parcours, mais ne clôt pas le
|
||||||
|
ticket #109 et ne couvre pas les limitations encore listées dans la roadmap.
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,23 @@
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
La tranche 3 du ticket #109 ajoute l’OCR contrôlé des documents d’identité dans
|
Le ticket #109 reste en cours. Ses tranches livrées couvrent l’OCR contrôlé et
|
||||||
l’assistant de création : lancement explicite, staging, révision champ par
|
révisable dans la création d’une personne, l’import normal et la fiche de
|
||||||
champ et persistance transactionnelle V15. Elle ne clôt pas le ticket #109 et
|
preuve, la transcription corrigée persistante, l’historique multi-run, la
|
||||||
n’engage aucune tranche suivante.
|
provenance graphique, ainsi que l’aperçu avancé partagé. L’import multiple
|
||||||
La provenance graphique, les langues Tesseract dynamiques et la compensation
|
reste sans OCR groupé : chaque preuve est analysée individuellement après
|
||||||
complète des répertoires OCR font partie de cette même tranche.
|
import afin de préserver une association non ambiguë.
|
||||||
La lecture structurée V15 et les scénarios de fermeture/réouverture restent
|
|
||||||
également circonscrits à la tranche 3.
|
|
||||||
L’OCR contrôlé est limité à une preuve par opération dans l’import normal.
|
|
||||||
L’import multiple sans OCR reste disponible. L’OCR multiple constitue un
|
|
||||||
chantier futur distinct ; les preuves sont actuellement analysées
|
|
||||||
successivement afin de conserver une association non ambiguë.
|
|
||||||
|
|
||||||
La tranche 2 partage désormais l’aperçu entre l’assistant personne et la
|
L’aperçu couvre PNG/JPEG, HEIC/HEIF, MP4/MOV, texte, EML passif et PDF
|
||||||
fiche directe. La validation manuelle des codecs installés reste nécessaire ;
|
multipage, avec zoom, ajustement, défilements, navigation et compteur. La
|
||||||
elle ne vaut pas achèvement du ticket #109 et n’inclut aucun OCR.
|
politique commune des dialogues métiers fournit une géométrie responsive, un
|
||||||
|
formulaire défilable, un aperçu redimensionnable et des actions fixes.
|
||||||
|
|
||||||
L’aperçu contrôlé du ticket #109 couvre PNG/JPEG, HEIC/HEIF, MP4/MOV, texte,
|
La validation manuelle de ces parcours est réussie. Elle ne vaut pas
|
||||||
EML passif et première page PDF. OCR et analyse avancée restent hors périmètre.
|
achèvement du ticket #109.
|
||||||
|
|
||||||
> **Dernière mise à jour :** 2026-07-28
|
> **Dernière mise à jour :** 2026-07-30
|
||||||
> **État du projet :** développement actif
|
> **État du projet :** développement actif
|
||||||
> **Schéma SQLite courant :** V14
|
> **Schéma SQLite courant :** V17
|
||||||
|
|
||||||
Les tranches 1 et 2 du ticket #109 couvrent les rôles contextuels multiples,
|
|
||||||
la sélection de plusieurs preuves existantes, le staging et l’import multiple
|
|
||||||
différé, ainsi que la création transactionnelle avec compensation des
|
|
||||||
fichiers. OCR, extraction de documents d’identité et analyses documentaires
|
|
||||||
avancées restent hors périmètre.
|
|
||||||
> **Usage opérationnel :** non prêt pour la production
|
> **Usage opérationnel :** non prêt pour la production
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -142,7 +131,7 @@ La branche `main` contient notamment :
|
||||||
|
|
||||||
- création, validation et ouverture d'enquêtes ;
|
- création, validation et ouverture d'enquêtes ;
|
||||||
- session d'enquête remplaçable proprement ;
|
- session d'enquête remplaçable proprement ;
|
||||||
- infrastructure SQLite et migrations jusqu'à V14 ;
|
- infrastructure SQLite et migrations jusqu'à V17 ;
|
||||||
- couche Database, DAO et services métier ;
|
- couche Database, DAO et services métier ;
|
||||||
- import de preuves avec copie contrôlée et SHA-256 ;
|
- import de preuves avec copie contrôlée et SHA-256 ;
|
||||||
- vérification d'intégrité et reclassement des preuves ;
|
- vérification d'intégrité et reclassement des preuves ;
|
||||||
|
|
@ -170,6 +159,40 @@ Cette liste est une synthèse et non un contrat de stabilité.
|
||||||
|
|
||||||
## 5. Chantier actif
|
## 5. Chantier actif
|
||||||
|
|
||||||
|
### Ticket #109 — Personnes, preuves et OCR d’identité
|
||||||
|
|
||||||
|
Statut technique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PARTIEL — VALIDATION MANUELLE DES TRANCHES LIVRÉES RÉUSSIE
|
||||||
|
```
|
||||||
|
|
||||||
|
Éléments livrés :
|
||||||
|
|
||||||
|
- sélection de preuves existantes, staging et import multiple différé ;
|
||||||
|
- OCR contrôlé, correction rééditable et transcription corrigée distincte ;
|
||||||
|
- persistance transactionnelle sur l’UUID définitif avec historique multi-run ;
|
||||||
|
- consultation, révision sans Tesseract et nouvelle analyse depuis la fiche ;
|
||||||
|
- provenance graphique et aperçu partagé avec PDF multipage ;
|
||||||
|
- politique commune de géométrie des dialogues GTK métiers.
|
||||||
|
|
||||||
|
Limitations restantes :
|
||||||
|
|
||||||
|
1. l’OCR groupé de plusieurs preuves dans une même opération n’est pas pris en
|
||||||
|
charge ;
|
||||||
|
2. le statut contrôlé d’authenticité du document et la justification
|
||||||
|
obligatoire des statuts affirmatifs ne sont pas implémentés ;
|
||||||
|
3. la relation factuelle typée entre personne et preuve et certains états
|
||||||
|
d’identification demandés restent incomplets ;
|
||||||
|
4. une partie du vocabulaire des rôles demeure codée en dur ;
|
||||||
|
5. la provenance uniforme de tous les dérivés et la projection des seules
|
||||||
|
valeurs OCR confirmées vers les attributs structurés de la personne restent
|
||||||
|
partielles ;
|
||||||
|
6. les tests négatifs garantissant l’absence de relation automatique
|
||||||
|
attribuant l’identité ou le rôle d’auteur doivent encore être renforcés.
|
||||||
|
|
||||||
|
Ces limites interdisent de présenter le ticket #109 comme terminé.
|
||||||
|
|
||||||
### Ticket #107 — Pivot e-mail forensique
|
### Ticket #107 — Pivot e-mail forensique
|
||||||
|
|
||||||
Statut technique :
|
Statut technique :
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ typedef struct {
|
||||||
char *expected_sha256;
|
char *expected_sha256;
|
||||||
char *mime_type;
|
char *mime_type;
|
||||||
guint64 request_generation;
|
guint64 request_generation;
|
||||||
|
guint pdf_page;
|
||||||
} EvidencePreviewRequest;
|
} EvidencePreviewRequest;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
EvidencePreviewKind kind;
|
EvidencePreviewKind kind;
|
||||||
|
|
@ -40,6 +41,7 @@ typedef struct {
|
||||||
char *message;
|
char *message;
|
||||||
guint64 size_bytes;
|
guint64 size_bytes;
|
||||||
guint item_count;
|
guint item_count;
|
||||||
|
guint current_page;
|
||||||
gboolean integrity_valid;
|
gboolean integrity_valid;
|
||||||
gboolean preview_available;
|
gboolean preview_available;
|
||||||
gboolean truncated;
|
gboolean truncated;
|
||||||
|
|
@ -47,6 +49,8 @@ typedef struct {
|
||||||
EvidencePreviewRequest *evidence_preview_request_new(const char *root,
|
EvidencePreviewRequest *evidence_preview_request_new(const char *root,
|
||||||
const char *evidence_identifier, const char *relative_path,
|
const char *evidence_identifier, const char *relative_path,
|
||||||
const char *sha256, const char *mime_type, guint64 generation);
|
const char *sha256, const char *mime_type, guint64 generation);
|
||||||
|
void evidence_preview_request_set_pdf_page(
|
||||||
|
EvidencePreviewRequest *request, guint page);
|
||||||
void evidence_preview_request_free(EvidencePreviewRequest *request);
|
void evidence_preview_request_free(EvidencePreviewRequest *request);
|
||||||
EvidencePreviewResult *evidence_preview_load(
|
EvidencePreviewResult *evidence_preview_load(
|
||||||
const EvidencePreviewRequest *request, GCancellable *cancellable,
|
const EvidencePreviewRequest *request, GCancellable *cancellable,
|
||||||
|
|
|
||||||
30
include/views/dialog_geometry.h
Normal file
30
include/views/dialog_geometry.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef LABFY_DIALOG_GEOMETRY_H
|
||||||
|
#define LABFY_DIALOG_GEOMETRY_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int initial_width;
|
||||||
|
int initial_height;
|
||||||
|
int minimum_width;
|
||||||
|
int minimum_height;
|
||||||
|
} LabfyDialogGeometry;
|
||||||
|
|
||||||
|
LabfyDialogGeometry labfy_dialog_geometry_calculate(
|
||||||
|
int workarea_width, int workarea_height);
|
||||||
|
|
||||||
|
void labfy_dialog_apply_standard_geometry(
|
||||||
|
GtkWindow *window, GtkWindow *parent);
|
||||||
|
|
||||||
|
void labfy_dialog_prepare(
|
||||||
|
GtkWindow *window,
|
||||||
|
GtkWindow *parent,
|
||||||
|
gboolean modal,
|
||||||
|
gboolean destroy_with_parent);
|
||||||
|
|
||||||
|
void labfy_dialog_present(GtkWindow *window);
|
||||||
|
|
||||||
|
void labfy_paned_apply_initial_ratio(
|
||||||
|
GtkPaned *paned, double ratio, int minimum_start, int minimum_end);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -25,6 +25,19 @@ const char *evidence_preview_widget_get_state(
|
||||||
const EvidencePreviewWidget *widget);
|
const EvidencePreviewWidget *widget);
|
||||||
char *evidence_preview_widget_dup_text(
|
char *evidence_preview_widget_dup_text(
|
||||||
const EvidencePreviewWidget *widget);
|
const EvidencePreviewWidget *widget);
|
||||||
|
void evidence_preview_widget_zoom_in(EvidencePreviewWidget *widget);
|
||||||
|
void evidence_preview_widget_zoom_out(EvidencePreviewWidget *widget);
|
||||||
|
void evidence_preview_widget_fit(EvidencePreviewWidget *widget);
|
||||||
|
double evidence_preview_widget_get_zoom(
|
||||||
|
const EvidencePreviewWidget *widget);
|
||||||
|
gboolean evidence_preview_widget_is_fit(
|
||||||
|
const EvidencePreviewWidget *widget);
|
||||||
|
gboolean evidence_preview_widget_set_pdf_page(
|
||||||
|
EvidencePreviewWidget *widget, guint page);
|
||||||
|
guint evidence_preview_widget_get_pdf_page(
|
||||||
|
const EvidencePreviewWidget *widget);
|
||||||
|
guint evidence_preview_widget_get_pdf_page_count(
|
||||||
|
const EvidencePreviewWidget *widget);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,13 @@ void ocr_provenance_overlay_set_field(OcrProvenanceOverlay *overlay,
|
||||||
const IdentityFieldObservation *field, guint64 generation);
|
const IdentityFieldObservation *field, guint64 generation);
|
||||||
void ocr_provenance_overlay_clear(OcrProvenanceOverlay *overlay,
|
void ocr_provenance_overlay_clear(OcrProvenanceOverlay *overlay,
|
||||||
guint64 generation);
|
guint64 generation);
|
||||||
|
void ocr_provenance_overlay_set_page(
|
||||||
|
OcrProvenanceOverlay *overlay, guint page, guint64 generation);
|
||||||
|
void ocr_provenance_overlay_zoom_in(OcrProvenanceOverlay *overlay);
|
||||||
|
void ocr_provenance_overlay_zoom_out(OcrProvenanceOverlay *overlay);
|
||||||
|
void ocr_provenance_overlay_fit(OcrProvenanceOverlay *overlay);
|
||||||
|
double ocr_provenance_overlay_get_zoom(
|
||||||
|
const OcrProvenanceOverlay *overlay);
|
||||||
gboolean ocr_provenance_overlay_has_region(
|
gboolean ocr_provenance_overlay_has_region(
|
||||||
const OcrProvenanceOverlay *overlay);
|
const OcrProvenanceOverlay *overlay);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,8 +299,8 @@ failed:
|
||||||
if (handle != NULL) heif_image_handle_release(handle);
|
if (handle != NULL) heif_image_handle_release(handle);
|
||||||
heif_context_free(context); return FALSE;
|
heif_context_free(context); return FALSE;
|
||||||
}
|
}
|
||||||
static gboolean load_pdf(const char *path, EvidencePreviewResult *result,
|
static gboolean load_pdf(const char *path, guint requested_page,
|
||||||
GError **error)
|
EvidencePreviewResult *result, GError **error)
|
||||||
{
|
{
|
||||||
char *uri = g_filename_to_uri(path, NULL, error);
|
char *uri = g_filename_to_uri(path, NULL, error);
|
||||||
if (uri == NULL) return FALSE;
|
if (uri == NULL) return FALSE;
|
||||||
|
|
@ -309,7 +309,21 @@ static gboolean load_pdf(const char *path, EvidencePreviewResult *result,
|
||||||
gint pages = poppler_document_get_n_pages(document);
|
gint pages = poppler_document_get_n_pages(document);
|
||||||
if (pages < 1) { g_object_unref(document); g_set_error_literal(error,
|
if (pages < 1) { g_object_unref(document); g_set_error_literal(error,
|
||||||
preview_error(), 7, "Le PDF ne contient aucune page."); return FALSE; }
|
preview_error(), 7, "Le PDF ne contient aucune page."); return FALSE; }
|
||||||
PopplerPage *page = poppler_document_get_page(document, 0);
|
if (requested_page >= (guint) pages) {
|
||||||
|
g_object_unref(document);
|
||||||
|
g_set_error(error, preview_error(), 8,
|
||||||
|
"La page PDF %u est hors limites (document de %d pages).",
|
||||||
|
requested_page + 1U, pages);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
PopplerPage *page = poppler_document_get_page(
|
||||||
|
document, (gint) requested_page);
|
||||||
|
if (page == NULL) {
|
||||||
|
g_object_unref(document);
|
||||||
|
g_set_error_literal(error, preview_error(), 7,
|
||||||
|
"La page PDF demandée est indisponible.");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
double page_width = 0, page_height = 0;
|
double page_width = 0, page_height = 0;
|
||||||
poppler_page_get_size(page, &page_width, &page_height);
|
poppler_page_get_size(page, &page_width, &page_height);
|
||||||
if (!valid_dimensions((gint) page_width, (gint) page_height, error)) {
|
if (!valid_dimensions((gint) page_width, (gint) page_height, error)) {
|
||||||
|
|
@ -324,6 +338,7 @@ static gboolean load_pdf(const char *path, EvidencePreviewResult *result,
|
||||||
cairo_t *cr = cairo_create(surface); cairo_set_source_rgb(cr, 1, 1, 1);
|
cairo_t *cr = cairo_create(surface); cairo_set_source_rgb(cr, 1, 1, 1);
|
||||||
cairo_paint(cr); cairo_scale(cr, scale, scale); poppler_page_render(page, cr);
|
cairo_paint(cr); cairo_scale(cr, scale, scale); poppler_page_render(page, cr);
|
||||||
cairo_destroy(cr); result->item_count = (guint) pages;
|
cairo_destroy(cr); result->item_count = (guint) pages;
|
||||||
|
result->current_page = requested_page + 1U;
|
||||||
gboolean ok = surface_to_result(surface, width, height, result, error);
|
gboolean ok = surface_to_result(surface, width, height, result, error);
|
||||||
cairo_surface_destroy(surface); g_object_unref(page);
|
cairo_surface_destroy(surface); g_object_unref(page);
|
||||||
g_object_unref(document); return ok;
|
g_object_unref(document); return ok;
|
||||||
|
|
@ -396,6 +411,12 @@ EvidencePreviewRequest *evidence_preview_request_new(const char *root,
|
||||||
request->mime_type = g_strdup(mime_type);
|
request->mime_type = g_strdup(mime_type);
|
||||||
request->request_generation = generation; return request;
|
request->request_generation = generation; return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void evidence_preview_request_set_pdf_page(
|
||||||
|
EvidencePreviewRequest *request, guint page)
|
||||||
|
{
|
||||||
|
if (request != NULL) request->pdf_page = page;
|
||||||
|
}
|
||||||
void evidence_preview_request_free(EvidencePreviewRequest *request)
|
void evidence_preview_request_free(EvidencePreviewRequest *request)
|
||||||
{
|
{
|
||||||
if (request == NULL) return;
|
if (request == NULL) return;
|
||||||
|
|
@ -501,7 +522,7 @@ EvidencePreviewResult *evidence_preview_load(
|
||||||
result->kind = EVIDENCE_PREVIEW_KIND_PDF;
|
result->kind = EVIDENCE_PREVIEW_KIND_PDF;
|
||||||
result->effective_mime_type = g_strdup("application/pdf");
|
result->effective_mime_type = g_strdup("application/pdf");
|
||||||
result->effective_format = g_strdup("PDF");
|
result->effective_format = g_strdup("PDF");
|
||||||
ok = load_pdf(path, result, error); break;
|
ok = load_pdf(path, request->pdf_page, result, error); break;
|
||||||
default:
|
default:
|
||||||
result->message = g_strdup("Format non pris en charge ou contenu invalide.");
|
result->message = g_strdup("Format non pris en charge ou contenu invalide.");
|
||||||
result->effective_format = g_strdup("Inconnu"); break;
|
result->effective_format = g_strdup("Inconnu"); break;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
|
|
||||||
static EvidencePreviewRequest *copy_request(const EvidencePreviewRequest *r)
|
static EvidencePreviewRequest *copy_request(const EvidencePreviewRequest *r)
|
||||||
{
|
{
|
||||||
return evidence_preview_request_new(r->investigation_root_path,
|
EvidencePreviewRequest *copy = evidence_preview_request_new(
|
||||||
|
r->investigation_root_path,
|
||||||
r->evidence_identifier, r->relative_path, r->expected_sha256,
|
r->evidence_identifier, r->relative_path, r->expected_sha256,
|
||||||
r->mime_type, r->request_generation);
|
r->mime_type, r->request_generation);
|
||||||
|
evidence_preview_request_set_pdf_page(copy, r->pdf_page);
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
static gboolean worker(BackgroundTask *task, GCancellable *cancellable,
|
static gboolean worker(BackgroundTask *task, GCancellable *cancellable,
|
||||||
gpointer data, gpointer *result, GError **error)
|
gpointer data, gpointer *result, GError **error)
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,81 @@
|
||||||
#include "core/identity_field_extractor.h"
|
#include "core/identity_field_extractor.h"
|
||||||
#include "core/ocr_analysis.h"
|
#include "core/ocr_analysis.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static gboolean identity_ocr_workflow_languages_are_valid(
|
||||||
|
const char *languages)
|
||||||
|
{
|
||||||
|
char **codes;
|
||||||
|
gboolean valid = TRUE;
|
||||||
|
|
||||||
|
if (languages == NULL || languages[0] == '\0')
|
||||||
|
return FALSE;
|
||||||
|
codes = g_strsplit(languages, "+", -1);
|
||||||
|
for (guint index = 0; codes[index] != NULL && valid; index++) {
|
||||||
|
const char *code = codes[index];
|
||||||
|
|
||||||
|
valid = strlen(code) == 3;
|
||||||
|
for (guint character = 0; valid && code[character] != '\0';
|
||||||
|
character++)
|
||||||
|
valid = g_ascii_islower(code[character]);
|
||||||
|
}
|
||||||
|
g_strfreev(codes);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean identity_ocr_workflow_validate_request(
|
||||||
|
const IdentityOcrWorkflowRequest *request,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
#define REQUIRE_REQUEST_FIELD(condition, message) \
|
||||||
|
G_STMT_START { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
g_set_error_literal(error, G_IO_ERROR, \
|
||||||
|
G_IO_ERROR_INVALID_ARGUMENT, (message)); \
|
||||||
|
return FALSE; \
|
||||||
|
} \
|
||||||
|
} G_STMT_END
|
||||||
|
|
||||||
|
REQUIRE_REQUEST_FIELD(request != NULL,
|
||||||
|
"La demande OCR d’identité est absente.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->root_path != NULL,
|
||||||
|
"Le chemin racine de l’enquête est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->evidence_identifier != NULL,
|
||||||
|
"L’UUID de la preuve est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->relative_path != NULL,
|
||||||
|
"Le chemin relatif de la preuve est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->expected_sha256 != NULL,
|
||||||
|
"Le SHA-256 attendu est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->mime_type != NULL,
|
||||||
|
"Le type MIME de la preuve est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(
|
||||||
|
identity_ocr_document_type_is_valid(request->document_type),
|
||||||
|
"Le type de document d’identité est invalide dans la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(
|
||||||
|
identity_ocr_document_side_is_valid(request->document_side),
|
||||||
|
"La face du document d’identité est invalide dans la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(
|
||||||
|
identity_ocr_workflow_languages_are_valid(request->languages),
|
||||||
|
"Le code de langue Tesseract est invalide dans la demande OCR "
|
||||||
|
"(un code ISO 639-3 comme « fra » ou « eng » est requis).");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->preprocessing_profile != NULL,
|
||||||
|
"Le profil de prétraitement est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->tesseract_executable != NULL,
|
||||||
|
"Le chemin de l’exécutable Tesseract est absent de la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->page_number > 0,
|
||||||
|
"Le numéro de page est invalide dans la demande OCR.");
|
||||||
|
REQUIRE_REQUEST_FIELD(request->profile <= IDENTITY_OCR_PREPROCESS_UPSCALE,
|
||||||
|
"Le profil de prétraitement est invalide dans la demande OCR.");
|
||||||
|
|
||||||
|
#undef REQUIRE_REQUEST_FIELD
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean identity_ocr_workflow_request_is_valid(
|
gboolean identity_ocr_workflow_request_is_valid(
|
||||||
const IdentityOcrWorkflowRequest *request)
|
const IdentityOcrWorkflowRequest *request)
|
||||||
{
|
{
|
||||||
return request != NULL &&
|
return identity_ocr_workflow_validate_request(request, NULL);
|
||||||
request->root_path != NULL &&
|
|
||||||
request->evidence_identifier != NULL &&
|
|
||||||
request->relative_path != NULL &&
|
|
||||||
request->expected_sha256 != NULL &&
|
|
||||||
request->mime_type != NULL &&
|
|
||||||
identity_ocr_document_type_is_valid(request->document_type) &&
|
|
||||||
identity_ocr_document_side_is_valid(request->document_side) &&
|
|
||||||
request->languages != NULL &&
|
|
||||||
request->preprocessing_profile != NULL &&
|
|
||||||
request->tesseract_executable != NULL &&
|
|
||||||
request->page_number > 0 &&
|
|
||||||
request->profile <= IDENTITY_OCR_PREPROCESS_UPSCALE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentityOcrRun *identity_ocr_workflow_execute(
|
IdentityOcrRun *identity_ocr_workflow_execute(
|
||||||
|
|
@ -37,11 +96,8 @@ IdentityOcrRun *identity_ocr_workflow_execute(
|
||||||
gsize preview_length = 0;
|
gsize preview_length = 0;
|
||||||
|
|
||||||
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
||||||
if (!identity_ocr_workflow_request_is_valid(request)) {
|
if (!identity_ocr_workflow_validate_request(request, error))
|
||||||
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
|
||||||
"La demande du parcours OCR d’identité est invalide.");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
preview_request = evidence_preview_request_new(request->root_path,
|
preview_request = evidence_preview_request_new(request->root_path,
|
||||||
request->evidence_identifier, request->relative_path,
|
request->evidence_identifier, request->relative_path,
|
||||||
request->expected_sha256, request->mime_type, request->generation);
|
request->expected_sha256, request->mime_type, request->generation);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* @brief Formulaire GTK de création d'une personne observée.
|
* @brief Formulaire GTK de création d'une personne observée.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include "views/create_person_dialog.h"
|
#include "views/create_person_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
#include "core/evidence_staging.h"
|
#include "core/evidence_staging.h"
|
||||||
#include "core/evidence_staging_task.h"
|
#include "core/evidence_staging_task.h"
|
||||||
#include "core/person_confirmation_summary.h"
|
#include "core/person_confirmation_summary.h"
|
||||||
|
|
@ -49,6 +50,7 @@ typedef struct
|
||||||
EvidencePreviewWidget *preview;
|
EvidencePreviewWidget *preview;
|
||||||
GtkLabel *progress;
|
GtkLabel *progress;
|
||||||
GtkLabel *summary;
|
GtkLabel *summary;
|
||||||
|
GtkScrolledWindow *summary_scroll;
|
||||||
GtkStack *stack;
|
GtkStack *stack;
|
||||||
GtkButton *previous;
|
GtkButton *previous;
|
||||||
GtkButton *next;
|
GtkButton *next;
|
||||||
|
|
@ -132,25 +134,6 @@ static void create_person_dialog_on_retained_changed(
|
||||||
static void create_person_dialog_render_ocr_fields(
|
static void create_person_dialog_render_ocr_fields(
|
||||||
CreatePersonDialogState *state, IdentityOcrRun *run);
|
CreatePersonDialogState *state, IdentityOcrRun *run);
|
||||||
|
|
||||||
static gboolean create_person_dialog_set_initial_ocr_paned_position(
|
|
||||||
gpointer data)
|
|
||||||
{
|
|
||||||
GtkPaned *paned = GTK_PANED(data);
|
|
||||||
int width;
|
|
||||||
int position;
|
|
||||||
if (g_object_get_data(
|
|
||||||
G_OBJECT(paned), "create-person-ocr-position-set") != NULL)
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
width = gtk_widget_get_width(GTK_WIDGET(paned));
|
|
||||||
if (width <= 0) return G_SOURCE_CONTINUE;
|
|
||||||
position = MAX(500, (width * 2) / 3);
|
|
||||||
position = MIN(position, MAX(0, width - 180));
|
|
||||||
gtk_paned_set_position(paned, position);
|
|
||||||
g_object_set_data(G_OBJECT(paned),
|
|
||||||
"create-person-ocr-position-set", GINT_TO_POINTER(1));
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void identity_language_job_free(gpointer data)
|
static void identity_language_job_free(gpointer data)
|
||||||
{
|
{
|
||||||
IdentityLanguageJob *job = data;
|
IdentityLanguageJob *job = data;
|
||||||
|
|
@ -187,6 +170,53 @@ static const char *identity_language_label(const char *code)
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static guint identity_default_language_index(const GPtrArray *codes)
|
||||||
|
{
|
||||||
|
guint english_index = GTK_INVALID_LIST_POSITION;
|
||||||
|
|
||||||
|
for (guint index = 0; codes != NULL && index < codes->len; index++) {
|
||||||
|
const char *code = g_ptr_array_index((GPtrArray *) codes, index);
|
||||||
|
|
||||||
|
if (g_strcmp0(code, "fra") == 0)
|
||||||
|
return index;
|
||||||
|
if (g_strcmp0(code, "eng") == 0)
|
||||||
|
english_index = index;
|
||||||
|
}
|
||||||
|
return english_index != GTK_INVALID_LIST_POSITION
|
||||||
|
? english_index
|
||||||
|
: (codes != NULL && codes->len > 0
|
||||||
|
? 0
|
||||||
|
: GTK_INVALID_LIST_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *create_person_dialog_effective_ocr_mime(
|
||||||
|
const PersonEvidenceSelectionItem *item)
|
||||||
|
{
|
||||||
|
const char *mime =
|
||||||
|
person_evidence_selection_item_get_mime_type(item);
|
||||||
|
const char *name;
|
||||||
|
const char *suffix;
|
||||||
|
|
||||||
|
if (mime != NULL && mime[0] != '\0')
|
||||||
|
return mime;
|
||||||
|
name = person_evidence_selection_item_get_original_name(item);
|
||||||
|
suffix = name != NULL ? strrchr(name, '.') : NULL;
|
||||||
|
if (suffix == NULL)
|
||||||
|
return NULL;
|
||||||
|
if (g_ascii_strcasecmp(suffix, ".png") == 0)
|
||||||
|
return "image/png";
|
||||||
|
if (g_ascii_strcasecmp(suffix, ".jpg") == 0 ||
|
||||||
|
g_ascii_strcasecmp(suffix, ".jpeg") == 0)
|
||||||
|
return "image/jpeg";
|
||||||
|
if (g_ascii_strcasecmp(suffix, ".heic") == 0)
|
||||||
|
return "image/heic";
|
||||||
|
if (g_ascii_strcasecmp(suffix, ".heif") == 0)
|
||||||
|
return "image/heif";
|
||||||
|
if (g_ascii_strcasecmp(suffix, ".pdf") == 0)
|
||||||
|
return "application/pdf";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void identity_languages_completed(GObject *source,
|
static void identity_languages_completed(GObject *source,
|
||||||
GAsyncResult *result, gpointer data)
|
GAsyncResult *result, gpointer data)
|
||||||
{
|
{
|
||||||
|
|
@ -209,9 +239,7 @@ static void identity_languages_completed(GObject *source,
|
||||||
}
|
}
|
||||||
gtk_drop_down_set_model(state->ocr_languages, G_LIST_MODEL(labels));
|
gtk_drop_down_set_model(state->ocr_languages, G_LIST_MODEL(labels));
|
||||||
gtk_drop_down_set_selected(state->ocr_languages,
|
gtk_drop_down_set_selected(state->ocr_languages,
|
||||||
state->ocr_language_codes != NULL &&
|
identity_default_language_index(state->ocr_language_codes));
|
||||||
state->ocr_language_codes->len > 0 ? 0 :
|
|
||||||
GTK_INVALID_LIST_POSITION);
|
|
||||||
gtk_widget_set_sensitive(GTK_WIDGET(state->ocr_start),
|
gtk_widget_set_sensitive(GTK_WIDGET(state->ocr_start),
|
||||||
state->ocr_language_codes != NULL &&
|
state->ocr_language_codes != NULL &&
|
||||||
state->ocr_language_codes->len > 0);
|
state->ocr_language_codes->len > 0);
|
||||||
|
|
@ -598,6 +626,8 @@ static void create_person_dialog_ocr_completed(GObject *source,
|
||||||
create_person_dialog_render_ocr_fields(state, run);
|
create_person_dialog_render_ocr_fields(state, run);
|
||||||
ocr_provenance_overlay_set_image(state->ocr_overlay,
|
ocr_provenance_overlay_set_image(state->ocr_overlay,
|
||||||
identity_ocr_run_get_preview(run), state->ocr_generation);
|
identity_ocr_run_get_preview(run), state->ocr_generation);
|
||||||
|
ocr_provenance_overlay_set_page(state->ocr_overlay,
|
||||||
|
identity_ocr_run_get_page(run), state->ocr_generation);
|
||||||
const GPtrArray *fields = identity_ocr_run_get_fields(run);
|
const GPtrArray *fields = identity_ocr_run_get_fields(run);
|
||||||
if (fields != NULL && fields->len > 0)
|
if (fields != NULL && fields->len > 0)
|
||||||
ocr_provenance_overlay_set_field(state->ocr_overlay,
|
ocr_provenance_overlay_set_field(state->ocr_overlay,
|
||||||
|
|
@ -653,7 +683,7 @@ static void create_person_dialog_on_ocr_start(GtkButton *button,
|
||||||
job->identifier = g_strdup(
|
job->identifier = g_strdup(
|
||||||
person_evidence_selection_item_get_identifier(item));
|
person_evidence_selection_item_get_identifier(item));
|
||||||
job->sha256 = g_strdup(person_evidence_selection_item_get_sha256(item));
|
job->sha256 = g_strdup(person_evidence_selection_item_get_sha256(item));
|
||||||
job->mime = g_strdup(person_evidence_selection_item_get_mime_type(item));
|
job->mime = g_strdup(create_person_dialog_effective_ocr_mime(item));
|
||||||
if (person_evidence_selection_item_get_origin(item) ==
|
if (person_evidence_selection_item_get_origin(item) ==
|
||||||
PERSON_EVIDENCE_ORIGIN_EXISTING) {
|
PERSON_EVIDENCE_ORIGIN_EXISTING) {
|
||||||
record = person_evidence_selection_item_get_record(item);
|
record = person_evidence_selection_item_get_record(item);
|
||||||
|
|
@ -1430,6 +1460,10 @@ static void create_person_dialog_update_navigation(CreatePersonDialogState *stat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gtk_label_set_text(state->summary, confirmation->str);
|
gtk_label_set_text(state->summary, confirmation->str);
|
||||||
|
GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(
|
||||||
|
state->summary_scroll);
|
||||||
|
gtk_adjustment_set_value(adjustment,
|
||||||
|
gtk_adjustment_get_lower(adjustment));
|
||||||
g_string_free(confirmation, TRUE);
|
g_string_free(confirmation, TRUE);
|
||||||
g_free(text);
|
g_free(text);
|
||||||
g_free(notes);
|
g_free(notes);
|
||||||
|
|
@ -1524,9 +1558,7 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
gtk_window_set_application(state->window,
|
gtk_window_set_application(state->window,
|
||||||
gtk_window_get_application(parent));
|
gtk_window_get_application(parent));
|
||||||
gtk_window_set_title(state->window, "Ajouter une personne");
|
gtk_window_set_title(state->window, "Ajouter une personne");
|
||||||
gtk_window_set_transient_for(state->window, parent);
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(state->window, TRUE);
|
|
||||||
gtk_window_set_default_size(state->window, 960, 700);
|
|
||||||
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||||
gtk_widget_set_margin_start(box, 16); gtk_widget_set_margin_end(box, 16);
|
gtk_widget_set_margin_start(box, 16); gtk_widget_set_margin_end(box, 16);
|
||||||
gtk_widget_set_margin_top(box, 16); gtk_widget_set_margin_bottom(box, 16);
|
gtk_widget_set_margin_top(box, 16); gtk_widget_set_margin_bottom(box, 16);
|
||||||
|
|
@ -1554,6 +1586,8 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
}
|
}
|
||||||
state->evidence = GTK_DROP_DOWN(gtk_drop_down_new(
|
state->evidence = GTK_DROP_DOWN(gtk_drop_down_new(
|
||||||
G_LIST_MODEL(g_object_ref(state->evidence_labels)), NULL));
|
G_LIST_MODEL(g_object_ref(state->evidence_labels)), NULL));
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(state->evidence),
|
||||||
|
"create-person-evidence-dropdown");
|
||||||
state->search = GTK_ENTRY(gtk_entry_new());
|
state->search = GTK_ENTRY(gtk_entry_new());
|
||||||
gtk_entry_set_placeholder_text(state->search,
|
gtk_entry_set_placeholder_text(state->search,
|
||||||
"Rechercher par nom, description ou type");
|
"Rechercher par nom, description ou type");
|
||||||
|
|
@ -1565,6 +1599,8 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
state->type_filter = GTK_DROP_DOWN(
|
state->type_filter = GTK_DROP_DOWN(
|
||||||
gtk_drop_down_new(G_LIST_MODEL(
|
gtk_drop_down_new(G_LIST_MODEL(
|
||||||
g_object_ref(state->type_filter_labels)), NULL));
|
g_object_ref(state->type_filter_labels)), NULL));
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(state->type_filter),
|
||||||
|
"create-person-evidence-type-filter");
|
||||||
/*
|
/*
|
||||||
* gtk_drop_down_new() prend la propriété complète du modèle. Une
|
* gtk_drop_down_new() prend la propriété complète du modèle. Une
|
||||||
* libération ici laisserait les vues internes du GtkDropDown avec un
|
* libération ici laisserait les vues internes du GtkDropDown avec un
|
||||||
|
|
@ -1655,10 +1691,20 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
|
gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
|
||||||
gtk_widget_set_name(evidence_paned, "create-person-evidence-paned");
|
gtk_widget_set_name(evidence_paned, "create-person-evidence-paned");
|
||||||
gtk_widget_set_size_request(evidence_box, 320, -1);
|
gtk_widget_set_size_request(evidence_box, 320, -1);
|
||||||
gtk_paned_set_start_child(GTK_PANED(evidence_paned), evidence_box);
|
GtkWidget *evidence_scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_widget_set_name(evidence_scroll,
|
||||||
|
"create-person-evidence-scroll");
|
||||||
|
gtk_scrolled_window_set_policy(
|
||||||
|
GTK_SCROLLED_WINDOW(evidence_scroll),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_scrolled_window_set_child(
|
||||||
|
GTK_SCROLLED_WINDOW(evidence_scroll), evidence_box);
|
||||||
|
gtk_paned_set_start_child(
|
||||||
|
GTK_PANED(evidence_paned), evidence_scroll);
|
||||||
gtk_paned_set_end_child(GTK_PANED(evidence_paned),
|
gtk_paned_set_end_child(GTK_PANED(evidence_paned),
|
||||||
evidence_preview_widget_get_widget(state->preview));
|
evidence_preview_widget_get_widget(state->preview));
|
||||||
gtk_paned_set_position(GTK_PANED(evidence_paned), 420);
|
labfy_paned_apply_initial_ratio(
|
||||||
|
GTK_PANED(evidence_paned), 2.0 / 3.0, 480, 240);
|
||||||
gtk_paned_set_resize_start_child(
|
gtk_paned_set_resize_start_child(
|
||||||
GTK_PANED(evidence_paned), FALSE);
|
GTK_PANED(evidence_paned), FALSE);
|
||||||
gtk_paned_set_resize_end_child(GTK_PANED(evidence_paned), TRUE);
|
gtk_paned_set_resize_end_child(GTK_PANED(evidence_paned), TRUE);
|
||||||
|
|
@ -1689,6 +1735,8 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
"Détection des langues…", NULL};
|
"Détection des langues…", NULL};
|
||||||
state->ocr_languages = GTK_DROP_DOWN(
|
state->ocr_languages = GTK_DROP_DOWN(
|
||||||
gtk_drop_down_new_from_strings(pending_languages));
|
gtk_drop_down_new_from_strings(pending_languages));
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(state->ocr_languages),
|
||||||
|
"create-person-ocr-languages");
|
||||||
}
|
}
|
||||||
state->ocr_profile = GTK_DROP_DOWN(
|
state->ocr_profile = GTK_DROP_DOWN(
|
||||||
gtk_drop_down_new_from_strings(profile_labels));
|
gtk_drop_down_new_from_strings(profile_labels));
|
||||||
|
|
@ -1835,9 +1883,23 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
state->summary = GTK_LABEL(gtk_label_new(""));
|
state->summary = GTK_LABEL(gtk_label_new(""));
|
||||||
summary = GTK_WIDGET(state->summary);
|
summary = GTK_WIDGET(state->summary);
|
||||||
gtk_label_set_wrap(state->summary, TRUE);
|
gtk_label_set_wrap(state->summary, TRUE);
|
||||||
|
gtk_label_set_wrap_mode(state->summary, PANGO_WRAP_WORD_CHAR);
|
||||||
|
gtk_label_set_max_width_chars(state->summary, 100);
|
||||||
gtk_label_set_xalign(state->summary, 0.0f);
|
gtk_label_set_xalign(state->summary, 0.0f);
|
||||||
gtk_label_set_selectable(state->summary, TRUE);
|
gtk_label_set_selectable(state->summary, TRUE);
|
||||||
gtk_stack_add_titled(state->stack, summary, "summary", "5 — Confirmation");
|
gtk_widget_set_hexpand(summary, TRUE);
|
||||||
|
gtk_widget_set_valign(summary, GTK_ALIGN_START);
|
||||||
|
state->summary_scroll = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new());
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(state->summary_scroll),
|
||||||
|
"create-person-confirmation-scroll");
|
||||||
|
gtk_scrolled_window_set_policy(state->summary_scroll,
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_scrolled_window_set_propagate_natural_height(
|
||||||
|
state->summary_scroll, FALSE);
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(state->summary_scroll), TRUE);
|
||||||
|
gtk_scrolled_window_set_child(state->summary_scroll, summary);
|
||||||
|
gtk_stack_add_titled(state->stack,
|
||||||
|
GTK_WIDGET(state->summary_scroll), "summary", "5 — Confirmation");
|
||||||
state->error = GTK_LABEL(gtk_label_new(NULL)); gtk_widget_set_visible(GTK_WIDGET(state->error), FALSE);
|
state->error = GTK_LABEL(gtk_label_new(NULL)); gtk_widget_set_visible(GTK_WIDGET(state->error), FALSE);
|
||||||
actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
gtk_widget_set_name(actions, "create-person-actions");
|
gtk_widget_set_name(actions, "create-person-actions");
|
||||||
|
|
@ -1900,10 +1962,9 @@ gboolean create_person_dialog_present(GtkWindow *parent,
|
||||||
g_task_run_in_thread(task, identity_languages_worker);
|
g_task_run_in_thread(task, identity_languages_worker);
|
||||||
g_object_unref(task);
|
g_object_unref(task);
|
||||||
}
|
}
|
||||||
gtk_window_present(state->window);
|
labfy_paned_apply_initial_ratio(
|
||||||
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
|
GTK_PANED(ocr_paned), 2.0 / 3.0, 520, 240);
|
||||||
create_person_dialog_set_initial_ocr_paned_position,
|
labfy_dialog_present(state->window);
|
||||||
g_object_ref(ocr_paned), g_object_unref);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
void create_person_dialog_result_free(CreatePersonDialogResult *result)
|
void create_person_dialog_result_free(CreatePersonDialogResult *result)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/create_relation_dialog.h"
|
#include "views/create_relation_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
#include "models/entity_record.h"
|
#include "models/entity_record.h"
|
||||||
#include "models/evidence_record.h"
|
#include "models/evidence_record.h"
|
||||||
|
|
@ -1231,26 +1232,7 @@ static gboolean create_relation_dialog_present_internal(
|
||||||
: "Ajouter une relation"
|
: "Ajouter une relation"
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_window_set_transient_for(
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
state->window,
|
|
||||||
parent
|
|
||||||
);
|
|
||||||
|
|
||||||
gtk_window_set_modal(
|
|
||||||
state->window,
|
|
||||||
TRUE
|
|
||||||
);
|
|
||||||
|
|
||||||
gtk_window_set_destroy_with_parent(
|
|
||||||
state->window,
|
|
||||||
TRUE
|
|
||||||
);
|
|
||||||
|
|
||||||
gtk_window_set_default_size(
|
|
||||||
state->window,
|
|
||||||
920,
|
|
||||||
780
|
|
||||||
);
|
|
||||||
|
|
||||||
root_box =
|
root_box =
|
||||||
gtk_box_new(
|
gtk_box_new(
|
||||||
|
|
@ -1912,9 +1894,7 @@ static gboolean create_relation_dialog_present_internal(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_window_present(
|
labfy_dialog_present(state->window);
|
||||||
state->window
|
|
||||||
);
|
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/create_social_account_dialog.h"
|
#include "views/create_social_account_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
struct CreateSocialAccountDialogResult
|
struct CreateSocialAccountDialogResult
|
||||||
{
|
{
|
||||||
|
|
@ -200,9 +201,7 @@ gboolean create_social_account_dialog_present(
|
||||||
state->evidence_identifiers = g_ptr_array_new_with_free_func(g_free);
|
state->evidence_identifiers = g_ptr_array_new_with_free_func(g_free);
|
||||||
state->window = GTK_WINDOW(gtk_window_new());
|
state->window = GTK_WINDOW(gtk_window_new());
|
||||||
gtk_window_set_title(state->window, "Ajouter un compte social");
|
gtk_window_set_title(state->window, "Ajouter un compte social");
|
||||||
gtk_window_set_transient_for(state->window, parent);
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(state->window, TRUE);
|
|
||||||
gtk_window_set_default_size(state->window, 620, 560);
|
|
||||||
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||||
gtk_widget_set_margin_start(box, 16); gtk_widget_set_margin_end(box, 16);
|
gtk_widget_set_margin_start(box, 16); gtk_widget_set_margin_end(box, 16);
|
||||||
gtk_widget_set_margin_top(box, 16); gtk_widget_set_margin_bottom(box, 16);
|
gtk_widget_set_margin_top(box, 16); gtk_widget_set_margin_bottom(box, 16);
|
||||||
|
|
@ -266,7 +265,7 @@ gboolean create_social_account_dialog_present(
|
||||||
g_signal_connect(create, "clicked", G_CALLBACK(create_social_account_dialog_on_create), state);
|
g_signal_connect(create, "clicked", G_CALLBACK(create_social_account_dialog_on_create), state);
|
||||||
g_object_set_data_full(G_OBJECT(state->window), "social-account-state", state,
|
g_object_set_data_full(G_OBJECT(state->window), "social-account-state", state,
|
||||||
create_social_account_dialog_state_free);
|
create_social_account_dialog_state_free);
|
||||||
gtk_window_present(state->window);
|
labfy_dialog_present(state->window);
|
||||||
g_free(now_text); g_date_time_unref(now);
|
g_free(now_text); g_date_time_unref(now);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
145
src/views/dialog_geometry.c
Normal file
145
src/views/dialog_geometry.c
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
|
#define LABFY_DIALOG_INITIAL_WIDTH 1200
|
||||||
|
#define LABFY_DIALOG_INITIAL_HEIGHT 800
|
||||||
|
#define LABFY_DIALOG_MINIMUM_WIDTH 800
|
||||||
|
#define LABFY_DIALOG_MINIMUM_HEIGHT 600
|
||||||
|
#define LABFY_DIALOG_WORKAREA_FACTOR 0.90
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double ratio;
|
||||||
|
int minimum_start;
|
||||||
|
int minimum_end;
|
||||||
|
} LabfyPanedGeometry;
|
||||||
|
|
||||||
|
LabfyDialogGeometry labfy_dialog_geometry_calculate(
|
||||||
|
int workarea_width, int workarea_height)
|
||||||
|
{
|
||||||
|
LabfyDialogGeometry geometry = {0};
|
||||||
|
int usable_width = workarea_width > 0
|
||||||
|
? (workarea_width >= LABFY_DIALOG_MINIMUM_WIDTH
|
||||||
|
? workarea_width
|
||||||
|
: MAX(1, (int) (
|
||||||
|
workarea_width * LABFY_DIALOG_WORKAREA_FACTOR)))
|
||||||
|
: LABFY_DIALOG_INITIAL_WIDTH;
|
||||||
|
int usable_height = workarea_height > 0
|
||||||
|
? (workarea_height >= LABFY_DIALOG_MINIMUM_HEIGHT
|
||||||
|
? workarea_height
|
||||||
|
: MAX(1, (int) (
|
||||||
|
workarea_height * LABFY_DIALOG_WORKAREA_FACTOR)))
|
||||||
|
: LABFY_DIALOG_INITIAL_HEIGHT;
|
||||||
|
geometry.initial_width = MIN(LABFY_DIALOG_INITIAL_WIDTH, usable_width);
|
||||||
|
geometry.initial_height = MIN(LABFY_DIALOG_INITIAL_HEIGHT, usable_height);
|
||||||
|
geometry.minimum_width = workarea_width >= LABFY_DIALOG_MINIMUM_WIDTH
|
||||||
|
? LABFY_DIALOG_MINIMUM_WIDTH : usable_width;
|
||||||
|
geometry.minimum_height = workarea_height >= LABFY_DIALOG_MINIMUM_HEIGHT
|
||||||
|
? LABFY_DIALOG_MINIMUM_HEIGHT : usable_height;
|
||||||
|
geometry.minimum_width = MIN(
|
||||||
|
geometry.minimum_width, geometry.initial_width);
|
||||||
|
geometry.minimum_height = MIN(
|
||||||
|
geometry.minimum_height, geometry.initial_height);
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GdkMonitor *dialog_geometry_find_monitor(
|
||||||
|
GtkWindow *window, GtkWindow *parent)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
|
||||||
|
GtkNative *native = parent != NULL
|
||||||
|
? gtk_widget_get_native(GTK_WIDGET(parent)) : NULL;
|
||||||
|
GdkSurface *surface = native != NULL
|
||||||
|
? gtk_native_get_surface(native) : NULL;
|
||||||
|
if (surface != NULL) {
|
||||||
|
GdkMonitor *monitor =
|
||||||
|
gdk_display_get_monitor_at_surface(display, surface);
|
||||||
|
if (monitor != NULL) return g_object_ref(monitor);
|
||||||
|
}
|
||||||
|
GListModel *monitors = gdk_display_get_monitors(display);
|
||||||
|
return monitors != NULL && g_list_model_get_n_items(monitors) > 0
|
||||||
|
? g_list_model_get_item(monitors, 0) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void labfy_dialog_apply_standard_geometry(
|
||||||
|
GtkWindow *window, GtkWindow *parent)
|
||||||
|
{
|
||||||
|
GdkRectangle workarea = {0};
|
||||||
|
GdkMonitor *monitor;
|
||||||
|
LabfyDialogGeometry geometry;
|
||||||
|
if (!GTK_IS_WINDOW(window)) return;
|
||||||
|
monitor = dialog_geometry_find_monitor(window, parent);
|
||||||
|
if (monitor != NULL) gdk_monitor_get_geometry(monitor, &workarea);
|
||||||
|
geometry = labfy_dialog_geometry_calculate(
|
||||||
|
workarea.width, workarea.height);
|
||||||
|
gtk_window_set_resizable(window, TRUE);
|
||||||
|
gtk_window_set_default_size(window,
|
||||||
|
geometry.initial_width, geometry.initial_height);
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(window),
|
||||||
|
geometry.minimum_width, geometry.minimum_height);
|
||||||
|
g_clear_object(&monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void labfy_dialog_prepare(
|
||||||
|
GtkWindow *window,
|
||||||
|
GtkWindow *parent,
|
||||||
|
gboolean modal,
|
||||||
|
gboolean destroy_with_parent)
|
||||||
|
{
|
||||||
|
if (!GTK_IS_WINDOW(window)) return;
|
||||||
|
gtk_window_set_transient_for(window,
|
||||||
|
GTK_IS_WINDOW(parent) ? parent : NULL);
|
||||||
|
gtk_window_set_modal(window, modal);
|
||||||
|
gtk_window_set_destroy_with_parent(window,
|
||||||
|
destroy_with_parent && GTK_IS_WINDOW(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
void labfy_dialog_present(GtkWindow *window)
|
||||||
|
{
|
||||||
|
GtkWindow *parent;
|
||||||
|
|
||||||
|
if (!GTK_IS_WINDOW(window)) return;
|
||||||
|
parent = gtk_window_get_transient_for(window);
|
||||||
|
labfy_dialog_apply_standard_geometry(window, parent);
|
||||||
|
gtk_window_present(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void paned_geometry_apply(
|
||||||
|
GtkPaned *paned, LabfyPanedGeometry *geometry)
|
||||||
|
{
|
||||||
|
int width = gtk_widget_get_width(GTK_WIDGET(paned));
|
||||||
|
int position;
|
||||||
|
if (width <= 0 || geometry == NULL) return;
|
||||||
|
position = (int) (width * geometry->ratio);
|
||||||
|
position = MAX(geometry->minimum_start, position);
|
||||||
|
position = MIN(position, MAX(0, width - geometry->minimum_end));
|
||||||
|
gtk_paned_set_position(paned, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean paned_geometry_idle(gpointer data)
|
||||||
|
{
|
||||||
|
GtkPaned *paned = GTK_PANED(data);
|
||||||
|
LabfyPanedGeometry *geometry = g_object_get_data(
|
||||||
|
G_OBJECT(paned), "labfy-paned-initial-geometry");
|
||||||
|
if (geometry == NULL) return G_SOURCE_REMOVE;
|
||||||
|
if (gtk_widget_get_width(GTK_WIDGET(paned)) <= 0)
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
paned_geometry_apply(paned, geometry);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void labfy_paned_apply_initial_ratio(
|
||||||
|
GtkPaned *paned, double ratio, int minimum_start, int minimum_end)
|
||||||
|
{
|
||||||
|
LabfyPanedGeometry *geometry;
|
||||||
|
if (!GTK_IS_PANED(paned)) return;
|
||||||
|
if (g_object_get_data(
|
||||||
|
G_OBJECT(paned), "labfy-paned-initial-geometry") != NULL)
|
||||||
|
return;
|
||||||
|
geometry = g_new0(LabfyPanedGeometry, 1);
|
||||||
|
geometry->ratio = CLAMP(ratio, 0.1, 0.9);
|
||||||
|
geometry->minimum_start = MAX(0, minimum_start);
|
||||||
|
geometry->minimum_end = MAX(0, minimum_end);
|
||||||
|
g_object_set_data_full(G_OBJECT(paned),
|
||||||
|
"labfy-paned-initial-geometry", geometry, g_free);
|
||||||
|
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, paned_geometry_idle,
|
||||||
|
g_object_ref(paned), g_object_unref);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* @brief Présentation en lecture seule d'une analyse EML.
|
* @brief Présentation en lecture seule d'une analyse EML.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include "views/eml_analysis_dialog.h"
|
#include "views/eml_analysis_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
#include "widgets/controlled_vocab_dropdown.h"
|
#include "widgets/controlled_vocab_dropdown.h"
|
||||||
typedef struct { GtkWindow *window; GtkWidget *proposals_box;
|
typedef struct { GtkWindow *window; GtkWidget *proposals_box;
|
||||||
GtkWidget *summary_label;
|
GtkWidget *summary_label;
|
||||||
|
|
@ -234,8 +235,7 @@ void eml_analysis_dialog_present(GtkWindow *parent,
|
||||||
state->callback = callback; state->user_data = user_data;
|
state->callback = callback; state->user_data = user_data;
|
||||||
window = GTK_WINDOW(gtk_window_new());
|
window = GTK_WINDOW(gtk_window_new());
|
||||||
gtk_window_set_title(window, "Analyse locale du courriel EML");
|
gtk_window_set_title(window, "Analyse locale du courriel EML");
|
||||||
gtk_window_set_transient_for(window, parent); gtk_window_set_modal(window, TRUE);
|
labfy_dialog_prepare(window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_default_size(window, 720, 560);
|
|
||||||
state->window = window;
|
state->window = window;
|
||||||
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||||
gtk_widget_set_margin_start(box, 14); gtk_widget_set_margin_end(box, 14);
|
gtk_widget_set_margin_start(box, 14); gtk_widget_set_margin_end(box, 14);
|
||||||
|
|
@ -314,7 +314,7 @@ void eml_analysis_dialog_present(GtkWindow *parent,
|
||||||
G_CALLBACK(eml_analysis_dialog_on_window_close), state);
|
G_CALLBACK(eml_analysis_dialog_on_window_close), state);
|
||||||
g_object_set_data_full(G_OBJECT(window), "eml-dialog-state", state,
|
g_object_set_data_full(G_OBJECT(window), "eml-dialog-state", state,
|
||||||
eml_analysis_dialog_state_free);
|
eml_analysis_dialog_state_free);
|
||||||
gtk_window_present(window);
|
labfy_dialog_present(window);
|
||||||
g_free(received); g_free(emails); g_free(domains);
|
g_free(received); g_free(emails); g_free(domains);
|
||||||
g_free(sender_ips); g_free(destination_ips);
|
g_free(sender_ips); g_free(destination_ips);
|
||||||
}
|
}
|
||||||
|
|
@ -418,9 +418,7 @@ void eml_analysis_dialog_present_pipeline(
|
||||||
state->callback = callback; state->user_data = user_data;
|
state->callback = callback; state->user_data = user_data;
|
||||||
state->window = GTK_WINDOW(gtk_window_new());
|
state->window = GTK_WINDOW(gtk_window_new());
|
||||||
gtk_window_set_title(state->window, "Révision de l’analyse EML");
|
gtk_window_set_title(state->window, "Révision de l’analyse EML");
|
||||||
gtk_window_set_transient_for(state->window, parent);
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(state->window, TRUE);
|
|
||||||
gtk_window_set_default_size(state->window, 900, 700);
|
|
||||||
GtkWidget *outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
GtkWidget *outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||||
gtk_widget_set_margin_start(outer, 14);
|
gtk_widget_set_margin_start(outer, 14);
|
||||||
gtk_widget_set_margin_end(outer, 14);
|
gtk_widget_set_margin_end(outer, 14);
|
||||||
|
|
@ -538,7 +536,7 @@ void eml_analysis_dialog_present_pipeline(
|
||||||
G_CALLBACK(eml_analysis_dialog_on_window_close), state);
|
G_CALLBACK(eml_analysis_dialog_on_window_close), state);
|
||||||
g_object_set_data_full(G_OBJECT(state->window), "eml-dialog-state",
|
g_object_set_data_full(G_OBJECT(state->window), "eml-dialog-state",
|
||||||
state, eml_analysis_dialog_state_free);
|
state, eml_analysis_dialog_state_free);
|
||||||
gtk_window_present(state->window);
|
labfy_dialog_present(state->window);
|
||||||
g_free(received);
|
g_free(received);
|
||||||
g_string_free(attachments, TRUE);
|
g_string_free(attachments, TRUE);
|
||||||
g_string_free(texts, TRUE);
|
g_string_free(texts, TRUE);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "views/evidence_identity_ocr_dialog.h"
|
#include "views/evidence_identity_ocr_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
#include "core/file_hash.h"
|
#include "core/file_hash.h"
|
||||||
#include "core/identity_ocr_workflow.h"
|
#include "core/identity_ocr_workflow.h"
|
||||||
|
|
@ -129,24 +130,6 @@ static void on_reset_transcription(GtkButton *button, gpointer data)
|
||||||
gtk_widget_set_sensitive(GTK_WIDGET(state->save_transcription), TRUE);
|
gtk_widget_set_sensitive(GTK_WIDGET(state->save_transcription), TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean set_initial_paned_position(gpointer data)
|
|
||||||
{
|
|
||||||
GtkWidget *paned = data;
|
|
||||||
int width;
|
|
||||||
int position;
|
|
||||||
if (g_object_get_data(
|
|
||||||
G_OBJECT(paned), "identity-initial-position-set") != NULL)
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
width = gtk_widget_get_width(paned);
|
|
||||||
if (width <= 0) return G_SOURCE_CONTINUE;
|
|
||||||
position = MAX(520, (width * 2) / 3);
|
|
||||||
position = MIN(position, MAX(0, width - 180));
|
|
||||||
gtk_paned_set_position(GTK_PANED(paned), position);
|
|
||||||
g_object_set_data(G_OBJECT(paned),
|
|
||||||
"identity-initial-position-set", GINT_TO_POINTER(1));
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *mime_from_path(const char *path)
|
static const char *mime_from_path(const char *path)
|
||||||
{
|
{
|
||||||
const char *dot = path != NULL ? strrchr(path, '.') : NULL;
|
const char *dot = path != NULL ? strrchr(path, '.') : NULL;
|
||||||
|
|
@ -545,6 +528,8 @@ static void workflow_completed(GObject *source, GAsyncResult *result,
|
||||||
GTK_WIDGET(state->save_transcription), TRUE);
|
GTK_WIDGET(state->save_transcription), TRUE);
|
||||||
ocr_provenance_overlay_set_image(state->overlay,
|
ocr_provenance_overlay_set_image(state->overlay,
|
||||||
identity_ocr_run_get_preview(state->run), state->generation);
|
identity_ocr_run_get_preview(state->run), state->generation);
|
||||||
|
ocr_provenance_overlay_set_page(state->overlay,
|
||||||
|
identity_ocr_run_get_page(state->run), state->generation);
|
||||||
gtk_label_set_text(state->status,
|
gtk_label_set_text(state->status,
|
||||||
"OCR terminé. Vérifiez chaque proposition.");
|
"OCR terminé. Vérifiez chaque proposition.");
|
||||||
gtk_widget_set_sensitive(
|
gtk_widget_set_sensitive(
|
||||||
|
|
@ -668,11 +653,9 @@ gboolean evidence_identity_ocr_dialog_present(
|
||||||
state->window = GTK_WINDOW(gtk_window_new());
|
state->window = GTK_WINDOW(gtk_window_new());
|
||||||
gtk_window_set_application(state->window,
|
gtk_window_set_application(state->window,
|
||||||
gtk_window_get_application(parent));
|
gtk_window_get_application(parent));
|
||||||
gtk_window_set_transient_for(state->window, parent);
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(state->window, TRUE);
|
|
||||||
gtk_window_set_title(state->window,
|
gtk_window_set_title(state->window,
|
||||||
"Import — OCR d’identité facultatif");
|
"Import — OCR d’identité facultatif");
|
||||||
gtk_window_set_default_size(state->window, 1200, 800);
|
|
||||||
GtkWidget *root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
GtkWidget *root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||||
gtk_widget_set_name(root, "identity-form-content");
|
gtk_widget_set_name(root, "identity-form-content");
|
||||||
gtk_widget_set_margin_start(root, 14);
|
gtk_widget_set_margin_start(root, 14);
|
||||||
|
|
@ -881,9 +864,9 @@ gboolean evidence_identity_ocr_dialog_present(
|
||||||
state->tesseract_path != NULL);
|
state->tesseract_path != NULL);
|
||||||
if (state->tesseract_path != NULL &&
|
if (state->tesseract_path != NULL &&
|
||||||
!populate_languages(state, error)) goto failure_window;
|
!populate_languages(state, error)) goto failure_window;
|
||||||
gtk_window_present(state->window);
|
labfy_paned_apply_initial_ratio(
|
||||||
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
|
GTK_PANED(paned), 2.0 / 3.0, 520, 240);
|
||||||
set_initial_paned_position, g_object_ref(paned), g_object_unref);
|
labfy_dialog_present(state->window);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
failure_window:
|
failure_window:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/evidence_import_dialog.h"
|
#include "views/evidence_import_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
#include "models/evidence_type.h"
|
#include "models/evidence_type.h"
|
||||||
#include "core/file_hash.h"
|
#include "core/file_hash.h"
|
||||||
#include "core/task_manager.h"
|
#include "core/task_manager.h"
|
||||||
|
|
@ -974,10 +975,7 @@ gboolean evidence_import_dialog_present(
|
||||||
"Importer une preuve"
|
"Importer une preuve"
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_window_set_transient_for(
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
state->window,
|
|
||||||
parent
|
|
||||||
);
|
|
||||||
|
|
||||||
gtk_window_set_application(
|
gtk_window_set_application(
|
||||||
state->window,
|
state->window,
|
||||||
|
|
@ -986,22 +984,6 @@ gboolean evidence_import_dialog_present(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_window_set_modal(
|
|
||||||
state->window,
|
|
||||||
TRUE
|
|
||||||
);
|
|
||||||
|
|
||||||
gtk_window_set_destroy_with_parent(
|
|
||||||
state->window,
|
|
||||||
TRUE
|
|
||||||
);
|
|
||||||
|
|
||||||
gtk_window_set_default_size(
|
|
||||||
state->window,
|
|
||||||
1200,
|
|
||||||
800
|
|
||||||
);
|
|
||||||
|
|
||||||
root_box =
|
root_box =
|
||||||
gtk_box_new(
|
gtk_box_new(
|
||||||
GTK_ORIENTATION_VERTICAL,
|
GTK_ORIENTATION_VERTICAL,
|
||||||
|
|
@ -1411,10 +1393,26 @@ gboolean evidence_import_dialog_present(
|
||||||
button_box
|
button_box
|
||||||
);
|
);
|
||||||
|
|
||||||
|
GtkWidget *left_panel = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||||
|
GtkWidget *form_scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_widget_set_name(form_scroll, "evidence-import-form-scroll");
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(form_scroll),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(form_scroll, TRUE);
|
||||||
|
g_object_ref(button_box);
|
||||||
|
gtk_box_remove(GTK_BOX(root_box), button_box);
|
||||||
|
gtk_scrolled_window_set_child(
|
||||||
|
GTK_SCROLLED_WINDOW(form_scroll), root_box);
|
||||||
|
gtk_box_append(GTK_BOX(left_panel), form_scroll);
|
||||||
|
gtk_widget_set_name(button_box, "evidence-import-actions");
|
||||||
|
gtk_widget_set_margin_end(button_box, 18);
|
||||||
|
gtk_widget_set_margin_bottom(button_box, 12);
|
||||||
|
gtk_box_append(GTK_BOX(left_panel), button_box);
|
||||||
|
g_object_unref(button_box);
|
||||||
paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
|
paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
|
||||||
gtk_widget_set_name(paned, "evidence-import-paned");
|
gtk_widget_set_name(paned, "evidence-import-paned");
|
||||||
gtk_paned_set_start_child(GTK_PANED(paned), root_box);
|
gtk_paned_set_start_child(GTK_PANED(paned), left_panel);
|
||||||
gtk_widget_set_size_request(root_box, 320, -1);
|
gtk_widget_set_size_request(left_panel, 320, -1);
|
||||||
state->preview_task_manager = task_manager_new();
|
state->preview_task_manager = task_manager_new();
|
||||||
state->preview = evidence_preview_widget_new(
|
state->preview = evidence_preview_widget_new(
|
||||||
state->preview_task_manager, NULL, NULL);
|
state->preview_task_manager, NULL, NULL);
|
||||||
|
|
@ -1425,7 +1423,8 @@ gboolean evidence_import_dialog_present(
|
||||||
}
|
}
|
||||||
gtk_paned_set_end_child(GTK_PANED(paned),
|
gtk_paned_set_end_child(GTK_PANED(paned),
|
||||||
evidence_preview_widget_get_widget(state->preview));
|
evidence_preview_widget_get_widget(state->preview));
|
||||||
gtk_paned_set_position(GTK_PANED(paned), 420);
|
labfy_paned_apply_initial_ratio(
|
||||||
|
GTK_PANED(paned), 2.0 / 3.0, 480, 240);
|
||||||
gtk_paned_set_resize_start_child(GTK_PANED(paned), FALSE);
|
gtk_paned_set_resize_start_child(GTK_PANED(paned), FALSE);
|
||||||
gtk_paned_set_resize_end_child(GTK_PANED(paned), TRUE);
|
gtk_paned_set_resize_end_child(GTK_PANED(paned), TRUE);
|
||||||
gtk_window_set_child(state->window, paned);
|
gtk_window_set_child(state->window, paned);
|
||||||
|
|
@ -1500,9 +1499,7 @@ gboolean evidence_import_dialog_present(
|
||||||
import_button
|
import_button
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_window_present(
|
labfy_dialog_present(state->window);
|
||||||
state->window
|
|
||||||
);
|
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/evidence_metadata_dialog.h"
|
#include "views/evidence_metadata_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
#include "models/evidence_type.h"
|
#include "models/evidence_type.h"
|
||||||
#include "dao/identity_ocr_dao.h"
|
#include "dao/identity_ocr_dao.h"
|
||||||
|
|
@ -258,6 +259,8 @@ static void evidence_metadata_dialog_render_ocr(
|
||||||
|
|
||||||
IdentityOcrRunRecord *run = g_ptr_array_index(
|
IdentityOcrRunRecord *run = g_ptr_array_index(
|
||||||
context->ocr_runs, selected);
|
context->ocr_runs, selected);
|
||||||
|
ocr_provenance_overlay_set_page(context->ocr_overlay,
|
||||||
|
(guint) run->page_number, context->ocr_generation);
|
||||||
const char *selected_identifier = g_ptr_array_index(
|
const char *selected_identifier = g_ptr_array_index(
|
||||||
context->ocr_run_identifiers, selected);
|
context->ocr_run_identifiers, selected);
|
||||||
if (g_strcmp0(run->id, selected_identifier) != 0) return;
|
if (g_strcmp0(run->id, selected_identifier) != 0) return;
|
||||||
|
|
@ -492,13 +495,10 @@ gboolean evidence_metadata_dialog_present_with_ocr(
|
||||||
evidence_record_get_type_identifier(record)) == 0) selected = index;
|
evidence_record_get_type_identifier(record)) == 0) selected = index;
|
||||||
}
|
}
|
||||||
gtk_window_set_title(context->window, "Modifier les métadonnées");
|
gtk_window_set_title(context->window, "Modifier les métadonnées");
|
||||||
gtk_window_set_default_size(context->window, 560, 420);
|
labfy_dialog_prepare(context->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(context->window, TRUE);
|
|
||||||
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
|
||||||
if (parent != NULL) {
|
if (parent != NULL) {
|
||||||
gtk_window_set_application(context->window,
|
gtk_window_set_application(context->window,
|
||||||
gtk_window_get_application(parent));
|
gtk_window_get_application(parent));
|
||||||
gtk_window_set_transient_for(context->window, parent);
|
|
||||||
}
|
}
|
||||||
g_object_set_data_full(G_OBJECT(context->window), "evidence-metadata-context",
|
g_object_set_data_full(G_OBJECT(context->window), "evidence-metadata-context",
|
||||||
context, evidence_metadata_dialog_context_free);
|
context, evidence_metadata_dialog_context_free);
|
||||||
|
|
@ -577,7 +577,7 @@ gboolean evidence_metadata_dialog_present_with_ocr(
|
||||||
gtk_box_append(GTK_BOX(buttons), cancel); gtk_box_append(GTK_BOX(buttons), save);
|
gtk_box_append(GTK_BOX(buttons), cancel); gtk_box_append(GTK_BOX(buttons), save);
|
||||||
gtk_box_append(GTK_BOX(main_box), buttons);
|
gtk_box_append(GTK_BOX(main_box), buttons);
|
||||||
gtk_window_set_child(context->window, main_box);
|
gtk_window_set_child(context->window, main_box);
|
||||||
gtk_window_present(context->window);
|
labfy_dialog_present(context->window);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* @brief Dialogue de sélection des preuves associées à une entité.
|
* @brief Dialogue de sélection des preuves associées à une entité.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include "views/manage_entity_evidence_dialog.h"
|
#include "views/manage_entity_evidence_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
#include "widgets/evidence_preview_widget.h"
|
#include "widgets/evidence_preview_widget.h"
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
typedef struct
|
typedef struct
|
||||||
|
|
@ -116,9 +117,7 @@ gboolean manage_entity_evidence_dialog_present(GtkWindow *parent,
|
||||||
state->import_callback = import_callback;
|
state->import_callback = import_callback;
|
||||||
state->window = GTK_WINDOW(gtk_window_new());
|
state->window = GTK_WINDOW(gtk_window_new());
|
||||||
gtk_window_set_title(state->window, "Pièces jointes de la personne");
|
gtk_window_set_title(state->window, "Pièces jointes de la personne");
|
||||||
gtk_window_set_transient_for(state->window, parent);
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(state->window, TRUE);
|
|
||||||
gtk_window_set_default_size(state->window, 1200, 800);
|
|
||||||
root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||||
gtk_widget_set_margin_top(root, 16); gtk_widget_set_margin_bottom(root, 16);
|
gtk_widget_set_margin_top(root, 16); gtk_widget_set_margin_bottom(root, 16);
|
||||||
gtk_widget_set_margin_start(root, 16); gtk_widget_set_margin_end(root, 16);
|
gtk_widget_set_margin_start(root, 16); gtk_widget_set_margin_end(root, 16);
|
||||||
|
|
@ -178,7 +177,8 @@ gboolean manage_entity_evidence_dialog_present(GtkWindow *parent,
|
||||||
gtk_paned_set_start_child(GTK_PANED(content), scroll);
|
gtk_paned_set_start_child(GTK_PANED(content), scroll);
|
||||||
gtk_paned_set_end_child(GTK_PANED(content),
|
gtk_paned_set_end_child(GTK_PANED(content),
|
||||||
evidence_preview_widget_get_widget(state->preview));
|
evidence_preview_widget_get_widget(state->preview));
|
||||||
gtk_paned_set_position(GTK_PANED(content), 380);
|
labfy_paned_apply_initial_ratio(
|
||||||
|
GTK_PANED(content), 2.0 / 3.0, 480, 240);
|
||||||
gtk_paned_set_resize_start_child(GTK_PANED(content), FALSE);
|
gtk_paned_set_resize_start_child(GTK_PANED(content), FALSE);
|
||||||
gtk_paned_set_resize_end_child(GTK_PANED(content), TRUE);
|
gtk_paned_set_resize_end_child(GTK_PANED(content), TRUE);
|
||||||
save = gtk_button_new_with_label("Enregistrer les pièces jointes");
|
save = gtk_button_new_with_label("Enregistrer les pièces jointes");
|
||||||
|
|
@ -194,5 +194,5 @@ gboolean manage_entity_evidence_dialog_present(GtkWindow *parent,
|
||||||
g_signal_connect(save, "clicked", G_CALLBACK(manage_evidence_on_save), state);
|
g_signal_connect(save, "clicked", G_CALLBACK(manage_evidence_on_save), state);
|
||||||
g_signal_connect(import, "clicked", G_CALLBACK(manage_evidence_on_import), state);
|
g_signal_connect(import, "clicked", G_CALLBACK(manage_evidence_on_import), state);
|
||||||
g_signal_connect(state->window, "close-request", G_CALLBACK(manage_evidence_on_close), state);
|
g_signal_connect(state->window, "close-request", G_CALLBACK(manage_evidence_on_close), state);
|
||||||
gtk_window_present(state->window); return TRUE;
|
labfy_dialog_present(state->window); return TRUE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/metadata_analysis_dialog.h"
|
#include "views/metadata_analysis_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
static gboolean metadata_analysis_dialog_on_escape(
|
static gboolean metadata_analysis_dialog_on_escape(
|
||||||
GtkWidget *widget, GVariant *arguments, gpointer user_data)
|
GtkWidget *widget, GVariant *arguments, gpointer user_data)
|
||||||
|
|
@ -30,9 +31,7 @@ void metadata_analysis_dialog_present(GtkWindow *parent, const char *summary,
|
||||||
|
|
||||||
window = gtk_window_new();
|
window = gtk_window_new();
|
||||||
gtk_window_set_title(GTK_WINDOW(window), "Métadonnées de la preuve");
|
gtk_window_set_title(GTK_WINDOW(window), "Métadonnées de la preuve");
|
||||||
gtk_window_set_default_size(GTK_WINDOW(window), 760, 560);
|
labfy_dialog_prepare(GTK_WINDOW(window), parent, TRUE, TRUE);
|
||||||
gtk_window_set_modal(GTK_WINDOW(window), TRUE);
|
|
||||||
if (parent != NULL) gtk_window_set_transient_for(GTK_WINDOW(window), parent);
|
|
||||||
content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||||
gtk_widget_set_margin_top(content, 16);
|
gtk_widget_set_margin_top(content, 16);
|
||||||
gtk_widget_set_margin_bottom(content, 16);
|
gtk_widget_set_margin_bottom(content, 16);
|
||||||
|
|
@ -81,7 +80,7 @@ void metadata_analysis_dialog_present(GtkWindow *parent, const char *summary,
|
||||||
}
|
}
|
||||||
gtk_box_append(GTK_BOX(content), close_button);
|
gtk_box_append(GTK_BOX(content), close_button);
|
||||||
gtk_window_set_child(GTK_WINDOW(window), content);
|
gtk_window_set_child(GTK_WINDOW(window), content);
|
||||||
gtk_window_present(GTK_WINDOW(window));
|
labfy_dialog_present(GTK_WINDOW(window));
|
||||||
g_free(heading_text);
|
g_free(heading_text);
|
||||||
g_free(path_text);
|
g_free(path_text);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/osint_dns_review_dialog.h"
|
#include "views/osint_dns_review_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
#include "models/osint_dns_proposal.h"
|
#include "models/osint_dns_proposal.h"
|
||||||
|
|
||||||
|
|
@ -211,11 +212,7 @@ void osint_dns_review_dialog_present(
|
||||||
context->user_data = user_data;
|
context->user_data = user_data;
|
||||||
|
|
||||||
gtk_window_set_title(context->window, "Intégrer les propositions DNS");
|
gtk_window_set_title(context->window, "Intégrer les propositions DNS");
|
||||||
gtk_window_set_default_size(context->window, 720, 560);
|
labfy_dialog_prepare(context->window, parent_window, TRUE, TRUE);
|
||||||
gtk_window_set_modal(context->window, TRUE);
|
|
||||||
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
|
||||||
if (parent_window != NULL)
|
|
||||||
gtk_window_set_transient_for(context->window, parent_window);
|
|
||||||
g_object_set_data_full(
|
g_object_set_data_full(
|
||||||
G_OBJECT(context->window), "osint-dns-review-context", context,
|
G_OBJECT(context->window), "osint-dns-review-context", context,
|
||||||
osint_dns_review_dialog_context_free
|
osint_dns_review_dialog_context_free
|
||||||
|
|
@ -279,5 +276,5 @@ void osint_dns_review_dialog_present(
|
||||||
gtk_box_append(GTK_BOX(main_box), scrolled_window);
|
gtk_box_append(GTK_BOX(main_box), scrolled_window);
|
||||||
gtk_box_append(GTK_BOX(main_box), button_box);
|
gtk_box_append(GTK_BOX(main_box), button_box);
|
||||||
gtk_window_set_child(context->window, main_box);
|
gtk_window_set_child(context->window, main_box);
|
||||||
gtk_window_present(context->window);
|
labfy_dialog_present(context->window);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "views/osint_execution_history_dialog.h"
|
#include "views/osint_execution_history_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
#include "core/osint_execution_integrity.h"
|
#include "core/osint_execution_integrity.h"
|
||||||
#include "models/osint_execution_record.h"
|
#include "models/osint_execution_record.h"
|
||||||
|
|
@ -153,10 +154,7 @@ void osint_execution_history_dialog_present(
|
||||||
context->records = g_ptr_array_ref(records);
|
context->records = g_ptr_array_ref(records);
|
||||||
context->linked_objects = g_hash_table_ref(linked_objects);
|
context->linked_objects = g_hash_table_ref(linked_objects);
|
||||||
gtk_window_set_title(context->window, "Historique OSINT");
|
gtk_window_set_title(context->window, "Historique OSINT");
|
||||||
gtk_window_set_default_size(context->window, 980, 650);
|
labfy_dialog_prepare(context->window, parent_window, TRUE, TRUE);
|
||||||
gtk_window_set_modal(context->window, TRUE);
|
|
||||||
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
|
||||||
if (parent_window != NULL) gtk_window_set_transient_for(context->window, parent_window);
|
|
||||||
g_object_set_data_full(G_OBJECT(context->window), "osint-history-context",
|
g_object_set_data_full(G_OBJECT(context->window), "osint-history-context",
|
||||||
context, osint_execution_history_dialog_context_free);
|
context, osint_execution_history_dialog_context_free);
|
||||||
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||||
|
|
@ -214,5 +212,5 @@ void osint_execution_history_dialog_present(
|
||||||
gtk_window_set_child(context->window, main_box);
|
gtk_window_set_child(context->window, main_box);
|
||||||
osint_execution_history_dialog_on_record_clicked(
|
osint_execution_history_dialog_on_record_clicked(
|
||||||
GTK_BUTTON(gtk_widget_get_first_child(history_box)), context);
|
GTK_BUTTON(gtk_widget_get_first_child(history_box)), context);
|
||||||
gtk_window_present(context->window);
|
labfy_dialog_present(context->window);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* @brief Révision humaine des résultats OCR d'un RIB.
|
* @brief Révision humaine des résultats OCR d'un RIB.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include "views/rib_ocr_review_dialog.h"
|
#include "views/rib_ocr_review_dialog.h"
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
#include "core/iban_analyzer.h"
|
#include "core/iban_analyzer.h"
|
||||||
typedef struct { GtkWindow *window; GtkEntry *iban; GtkLabel *error;
|
typedef struct { GtkWindow *window; GtkEntry *iban; GtkLabel *error;
|
||||||
RibOcrReviewCallback callback; gpointer data; gboolean completed; } State;
|
RibOcrReviewCallback callback; gpointer data; gboolean completed; } State;
|
||||||
|
|
@ -36,8 +37,7 @@ void rib_ocr_review_dialog_present(GtkWindow *parent, const char *ocr_text,
|
||||||
state->data = user_data; state->iban = GTK_ENTRY(gtk_entry_new());
|
state->data = user_data; state->iban = GTK_ENTRY(gtk_entry_new());
|
||||||
state->error = GTK_LABEL(gtk_label_new(NULL));
|
state->error = GTK_LABEL(gtk_label_new(NULL));
|
||||||
gtk_window_set_title(state->window, "Réviser l’analyse OCR du RIB");
|
gtk_window_set_title(state->window, "Réviser l’analyse OCR du RIB");
|
||||||
gtk_window_set_transient_for(state->window, parent); gtk_window_set_modal(state->window, TRUE);
|
labfy_dialog_prepare(state->window, parent, TRUE, TRUE);
|
||||||
gtk_window_set_default_size(state->window, 760, 620);
|
|
||||||
gtk_widget_set_margin_top(root, 16); gtk_widget_set_margin_bottom(root, 16);
|
gtk_widget_set_margin_top(root, 16); gtk_widget_set_margin_bottom(root, 16);
|
||||||
gtk_widget_set_margin_start(root, 16); gtk_widget_set_margin_end(root, 16);
|
gtk_widget_set_margin_start(root, 16); gtk_widget_set_margin_end(root, 16);
|
||||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
|
gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
|
||||||
|
|
@ -55,5 +55,5 @@ void rib_ocr_review_dialog_present(GtkWindow *parent, const char *ocr_text,
|
||||||
g_object_set_data_full(G_OBJECT(state->window), "state", state, state_free);
|
g_object_set_data_full(G_OBJECT(state->window), "state", state, state_free);
|
||||||
g_signal_connect(confirm, "clicked", G_CALLBACK(on_confirm), state);
|
g_signal_connect(confirm, "clicked", G_CALLBACK(on_confirm), state);
|
||||||
g_signal_connect(state->window, "close-request", G_CALLBACK(on_close), state);
|
g_signal_connect(state->window, "close-request", G_CALLBACK(on_close), state);
|
||||||
gtk_window_present(state->window);
|
labfy_dialog_present(state->window);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,19 @@ struct EvidencePreviewWidget {
|
||||||
GtkWidget *root;
|
GtkWidget *root;
|
||||||
GtkLabel *metadata;
|
GtkLabel *metadata;
|
||||||
GtkLabel *status;
|
GtkLabel *status;
|
||||||
|
GtkWidget *toolbar;
|
||||||
GtkStack *stack;
|
GtkStack *stack;
|
||||||
GtkPicture *image;
|
GtkPicture *image;
|
||||||
GtkPicture *pdf;
|
GtkPicture *pdf;
|
||||||
|
GtkScrolledWindow *image_scroll;
|
||||||
|
GtkScrolledWindow *pdf_scroll;
|
||||||
|
GtkButton *zoom_out;
|
||||||
|
GtkButton *fit;
|
||||||
|
GtkButton *zoom_in;
|
||||||
|
GtkLabel *zoom_label;
|
||||||
|
GtkButton *previous_page;
|
||||||
|
GtkButton *next_page;
|
||||||
|
GtkLabel *page_label;
|
||||||
GtkVideo *video;
|
GtkVideo *video;
|
||||||
GtkTextView *text;
|
GtkTextView *text;
|
||||||
GtkTextView *email;
|
GtkTextView *email;
|
||||||
|
|
@ -19,6 +29,16 @@ struct EvidencePreviewWidget {
|
||||||
gpointer session_data;
|
gpointer session_data;
|
||||||
guint64 generation;
|
guint64 generation;
|
||||||
char *identifier;
|
char *identifier;
|
||||||
|
EvidencePreviewRequest *request;
|
||||||
|
char *metadata_text;
|
||||||
|
gint raster_width;
|
||||||
|
gint raster_height;
|
||||||
|
guint zoom_index;
|
||||||
|
gboolean fit_mode;
|
||||||
|
gboolean raster_available;
|
||||||
|
gboolean pdf_available;
|
||||||
|
guint pdf_page;
|
||||||
|
guint pdf_page_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -77,6 +97,148 @@ static void preview_widget_reset_content(EvidencePreviewWidget *widget)
|
||||||
gtk_text_buffer_set_text(gtk_text_view_get_buffer(widget->email), "", -1);
|
gtk_text_buffer_set_text(gtk_text_view_get_buffer(widget->email), "", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const double zoom_factors[] = {
|
||||||
|
0.25, 0.50, 0.75, 1.00, 1.25, 1.50, 2.00, 3.00, 4.00
|
||||||
|
};
|
||||||
|
|
||||||
|
static GtkPicture *preview_widget_active_picture(
|
||||||
|
EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
return widget->pdf_available ? widget->pdf : widget->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkScrolledWindow *preview_widget_active_scroll(
|
||||||
|
EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
return widget->pdf_available ? widget->pdf_scroll : widget->image_scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_update_page_controls(
|
||||||
|
EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
char *text = g_strdup_printf("Page %u / %u",
|
||||||
|
widget->pdf_page_count > 0 ? widget->pdf_page + 1U : 0U,
|
||||||
|
widget->pdf_page_count);
|
||||||
|
gtk_label_set_text(widget->page_label, text);
|
||||||
|
g_free(text);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->previous_page),
|
||||||
|
widget->pdf_available);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->next_page),
|
||||||
|
widget->pdf_available);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->page_label),
|
||||||
|
widget->pdf_available);
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(widget->previous_page),
|
||||||
|
widget->pdf_available && widget->pdf_page > 0);
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(widget->next_page),
|
||||||
|
widget->pdf_available &&
|
||||||
|
widget->pdf_page + 1U < widget->pdf_page_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_apply_zoom(EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
GtkPicture *picture;
|
||||||
|
GtkScrolledWindow *scroll;
|
||||||
|
double factor;
|
||||||
|
char *text;
|
||||||
|
if (widget == NULL) return;
|
||||||
|
picture = preview_widget_active_picture(widget);
|
||||||
|
scroll = preview_widget_active_scroll(widget);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->zoom_out),
|
||||||
|
widget->raster_available);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->fit),
|
||||||
|
widget->raster_available);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->zoom_in),
|
||||||
|
widget->raster_available);
|
||||||
|
gtk_widget_set_visible(GTK_WIDGET(widget->zoom_label),
|
||||||
|
widget->raster_available);
|
||||||
|
if (!widget->raster_available) return;
|
||||||
|
factor = zoom_factors[widget->zoom_index];
|
||||||
|
text = widget->fit_mode ? g_strdup("Ajusté") :
|
||||||
|
g_strdup_printf("%.0f %%", factor * 100.0);
|
||||||
|
gtk_label_set_text(widget->zoom_label, text);
|
||||||
|
g_free(text);
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(widget->zoom_out),
|
||||||
|
widget->fit_mode || widget->zoom_index > 0);
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(widget->zoom_in),
|
||||||
|
widget->fit_mode ||
|
||||||
|
widget->zoom_index + 1U < G_N_ELEMENTS(zoom_factors));
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(widget->fit),
|
||||||
|
!widget->fit_mode);
|
||||||
|
if (widget->fit_mode) {
|
||||||
|
gtk_picture_set_can_shrink(picture, TRUE);
|
||||||
|
gtk_picture_set_content_fit(picture, GTK_CONTENT_FIT_CONTAIN);
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(picture), -1, -1);
|
||||||
|
gtk_widget_set_hexpand(GTK_WIDGET(picture), TRUE);
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(picture), TRUE);
|
||||||
|
gtk_scrolled_window_set_policy(scroll,
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_NEVER);
|
||||||
|
} else {
|
||||||
|
gtk_picture_set_can_shrink(picture, FALSE);
|
||||||
|
gtk_picture_set_content_fit(picture, GTK_CONTENT_FIT_CONTAIN);
|
||||||
|
gtk_widget_set_hexpand(GTK_WIDGET(picture), FALSE);
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(picture), FALSE);
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(picture),
|
||||||
|
MAX(1, (gint) (widget->raster_width * factor)),
|
||||||
|
MAX(1, (gint) (widget->raster_height * factor)));
|
||||||
|
gtk_scrolled_window_set_policy(scroll,
|
||||||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void evidence_preview_widget_zoom_in(EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
if (widget == NULL || !widget->raster_available) return;
|
||||||
|
if (widget->fit_mode) {
|
||||||
|
widget->fit_mode = FALSE;
|
||||||
|
widget->zoom_index = 4;
|
||||||
|
} else if (widget->zoom_index + 1U < G_N_ELEMENTS(zoom_factors))
|
||||||
|
widget->zoom_index++;
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void evidence_preview_widget_zoom_out(EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
if (widget == NULL || !widget->raster_available) return;
|
||||||
|
if (widget->fit_mode) {
|
||||||
|
widget->fit_mode = FALSE;
|
||||||
|
widget->zoom_index = 2;
|
||||||
|
} else if (widget->zoom_index > 0)
|
||||||
|
widget->zoom_index--;
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void evidence_preview_widget_fit(EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
if (widget == NULL || !widget->raster_available) return;
|
||||||
|
widget->fit_mode = TRUE;
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
double evidence_preview_widget_get_zoom(
|
||||||
|
const EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
return widget != NULL && !widget->fit_mode
|
||||||
|
? zoom_factors[widget->zoom_index] : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean evidence_preview_widget_is_fit(
|
||||||
|
const EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
return widget != NULL && widget->fit_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint evidence_preview_widget_get_pdf_page(
|
||||||
|
const EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
return widget != NULL ? widget->pdf_page : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint evidence_preview_widget_get_pdf_page_count(
|
||||||
|
const EvidencePreviewWidget *widget)
|
||||||
|
{
|
||||||
|
return widget != NULL ? widget->pdf_page_count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
void evidence_preview_widget_cancel(EvidencePreviewWidget *widget)
|
void evidence_preview_widget_cancel(EvidencePreviewWidget *widget)
|
||||||
{
|
{
|
||||||
if (widget == NULL) return;
|
if (widget == NULL) return;
|
||||||
|
|
@ -89,6 +251,8 @@ void evidence_preview_widget_cancel(EvidencePreviewWidget *widget)
|
||||||
if (widget->root != NULL)
|
if (widget->root != NULL)
|
||||||
preview_widget_reset_content(widget);
|
preview_widget_reset_content(widget);
|
||||||
g_clear_pointer(&widget->identifier, g_free);
|
g_clear_pointer(&widget->identifier, g_free);
|
||||||
|
g_clear_pointer(&widget->request, evidence_preview_request_free);
|
||||||
|
g_clear_pointer(&widget->metadata_text, g_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
void evidence_preview_widget_clear(EvidencePreviewWidget *widget)
|
void evidence_preview_widget_clear(EvidencePreviewWidget *widget)
|
||||||
|
|
@ -98,6 +262,12 @@ void evidence_preview_widget_clear(EvidencePreviewWidget *widget)
|
||||||
gtk_label_set_text(widget->metadata, "");
|
gtk_label_set_text(widget->metadata, "");
|
||||||
gtk_label_set_text(widget->status, "Aucune preuve sélectionnée.");
|
gtk_label_set_text(widget->status, "Aucune preuve sélectionnée.");
|
||||||
gtk_stack_set_visible_child_name(widget->stack, "empty");
|
gtk_stack_set_visible_child_name(widget->stack, "empty");
|
||||||
|
widget->raster_available = FALSE;
|
||||||
|
widget->pdf_available = FALSE;
|
||||||
|
widget->pdf_page = 0;
|
||||||
|
widget->pdf_page_count = 0;
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
preview_widget_update_page_controls(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
||||||
|
|
@ -128,9 +298,25 @@ static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
||||||
result->kind == EVIDENCE_PREVIEW_KIND_PDF
|
result->kind == EVIDENCE_PREVIEW_KIND_PDF
|
||||||
? "pdf" : "image");
|
? "pdf" : "image");
|
||||||
gtk_label_set_text(widget->status, "Aperçu intègre.");
|
gtk_label_set_text(widget->status, "Aperçu intègre.");
|
||||||
|
widget->raster_available = TRUE;
|
||||||
|
widget->pdf_available =
|
||||||
|
result->kind == EVIDENCE_PREVIEW_KIND_PDF;
|
||||||
|
widget->raster_width = result->width;
|
||||||
|
widget->raster_height = result->height;
|
||||||
|
if (widget->pdf_available) {
|
||||||
|
widget->pdf_page_count = result->item_count;
|
||||||
|
widget->pdf_page = result->current_page - 1U;
|
||||||
|
} else {
|
||||||
|
widget->pdf_page_count = 0;
|
||||||
|
widget->pdf_page = 0;
|
||||||
|
}
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
preview_widget_update_page_controls(widget);
|
||||||
g_object_unref(texture);
|
g_object_unref(texture);
|
||||||
}
|
}
|
||||||
} else if (result->kind == EVIDENCE_PREVIEW_KIND_VIDEO) {
|
} else if (result->kind == EVIDENCE_PREVIEW_KIND_VIDEO) {
|
||||||
|
widget->raster_available = FALSE;
|
||||||
|
widget->pdf_available = FALSE;
|
||||||
GtkMediaStream *media = gtk_media_file_new_for_filename(
|
GtkMediaStream *media = gtk_media_file_new_for_filename(
|
||||||
result->controlled_path);
|
result->controlled_path);
|
||||||
gtk_media_stream_set_volume(media, 0.0);
|
gtk_media_stream_set_volume(media, 0.0);
|
||||||
|
|
@ -145,6 +331,8 @@ static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
||||||
gtk_label_set_text(widget->status, result->message);
|
gtk_label_set_text(widget->status, result->message);
|
||||||
} else if (result->kind == EVIDENCE_PREVIEW_KIND_TEXT ||
|
} else if (result->kind == EVIDENCE_PREVIEW_KIND_TEXT ||
|
||||||
result->kind == EVIDENCE_PREVIEW_KIND_EMAIL) {
|
result->kind == EVIDENCE_PREVIEW_KIND_EMAIL) {
|
||||||
|
widget->raster_available = FALSE;
|
||||||
|
widget->pdf_available = FALSE;
|
||||||
GtkTextView *view = result->kind == EVIDENCE_PREVIEW_KIND_EMAIL
|
GtkTextView *view = result->kind == EVIDENCE_PREVIEW_KIND_EMAIL
|
||||||
? widget->email : widget->text;
|
? widget->email : widget->text;
|
||||||
gtk_text_buffer_set_text(gtk_text_view_get_buffer(view),
|
gtk_text_buffer_set_text(gtk_text_view_get_buffer(view),
|
||||||
|
|
@ -155,6 +343,8 @@ static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
||||||
gtk_label_set_text(widget->status,
|
gtk_label_set_text(widget->status,
|
||||||
result->truncated ? "Aperçu tronqué." : "Aperçu intègre.");
|
result->truncated ? "Aperçu tronqué." : "Aperçu intègre.");
|
||||||
} else {
|
} else {
|
||||||
|
widget->raster_available = FALSE;
|
||||||
|
widget->pdf_available = FALSE;
|
||||||
gtk_stack_set_visible_child_name(widget->stack,
|
gtk_stack_set_visible_child_name(widget->stack,
|
||||||
result->kind == EVIDENCE_PREVIEW_KIND_UNSUPPORTED
|
result->kind == EVIDENCE_PREVIEW_KIND_UNSUPPORTED
|
||||||
? "unsupported" : "error");
|
? "unsupported" : "error");
|
||||||
|
|
@ -162,6 +352,10 @@ static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
||||||
result->message != NULL ? result->message :
|
result->message != NULL ? result->message :
|
||||||
"Aperçu illisible.");
|
"Aperçu illisible.");
|
||||||
}
|
}
|
||||||
|
if (!widget->raster_available) {
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
preview_widget_update_page_controls(widget);
|
||||||
|
}
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
gtk_stack_set_visible_child_name(widget->stack, "error");
|
gtk_stack_set_visible_child_name(widget->stack, "error");
|
||||||
gtk_label_set_text(widget->status, error->message);
|
gtk_label_set_text(widget->status, error->message);
|
||||||
|
|
@ -182,6 +376,100 @@ static void preview_widget_completed(BackgroundTask *task, gpointer data)
|
||||||
g_clear_object(&root);
|
g_clear_object(&root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void preview_widget_start_request(EvidencePreviewWidget *widget,
|
||||||
|
guint pdf_page)
|
||||||
|
{
|
||||||
|
EvidencePreviewRequest *owned;
|
||||||
|
PreviewWidgetContext *context;
|
||||||
|
GError *error = NULL;
|
||||||
|
if (widget == NULL || widget->request == NULL) return;
|
||||||
|
widget->generation++;
|
||||||
|
if (widget->task != NULL) {
|
||||||
|
background_task_cancel(widget->task);
|
||||||
|
background_task_unref(widget->task);
|
||||||
|
widget->task = NULL;
|
||||||
|
}
|
||||||
|
owned = evidence_preview_request_new(
|
||||||
|
widget->request->investigation_root_path,
|
||||||
|
widget->request->evidence_identifier,
|
||||||
|
widget->request->relative_path,
|
||||||
|
widget->request->expected_sha256,
|
||||||
|
widget->request->mime_type, widget->generation);
|
||||||
|
evidence_preview_request_set_pdf_page(owned, pdf_page);
|
||||||
|
gtk_label_set_text(widget->metadata,
|
||||||
|
widget->metadata_text != NULL ? widget->metadata_text : "");
|
||||||
|
gtk_label_set_text(widget->status,
|
||||||
|
"Détection, vérification et chargement en cours…");
|
||||||
|
gtk_stack_set_visible_child_name(widget->stack, "loading");
|
||||||
|
context = g_new0(PreviewWidgetContext, 1);
|
||||||
|
g_weak_ref_init(&context->root, G_OBJECT(widget->root));
|
||||||
|
context->identifier = g_strdup(widget->identifier);
|
||||||
|
context->generation = widget->generation;
|
||||||
|
if (owned != NULL)
|
||||||
|
widget->task = evidence_preview_task_start(widget->task_manager,
|
||||||
|
owned, preview_widget_completed, context,
|
||||||
|
preview_widget_context_free, &error);
|
||||||
|
evidence_preview_request_free(owned);
|
||||||
|
if (widget->task == NULL) {
|
||||||
|
preview_widget_context_free(context);
|
||||||
|
gtk_stack_set_visible_child_name(widget->stack, "error");
|
||||||
|
gtk_label_set_text(widget->status,
|
||||||
|
error != NULL ? error->message : "Aperçu impossible.");
|
||||||
|
}
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean evidence_preview_widget_set_pdf_page(
|
||||||
|
EvidencePreviewWidget *widget, guint page)
|
||||||
|
{
|
||||||
|
if (widget == NULL || !widget->pdf_available ||
|
||||||
|
page >= widget->pdf_page_count || page == widget->pdf_page)
|
||||||
|
return FALSE;
|
||||||
|
widget->pdf_page = page;
|
||||||
|
preview_widget_update_page_controls(widget);
|
||||||
|
preview_widget_start_request(widget, page);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_on_zoom_out(
|
||||||
|
GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
(void) button;
|
||||||
|
evidence_preview_widget_zoom_out(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_on_fit(GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
(void) button;
|
||||||
|
evidence_preview_widget_fit(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_on_zoom_in(
|
||||||
|
GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
(void) button;
|
||||||
|
evidence_preview_widget_zoom_in(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_on_previous(
|
||||||
|
GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
EvidencePreviewWidget *widget = data;
|
||||||
|
(void) button;
|
||||||
|
if (widget->pdf_page > 0)
|
||||||
|
evidence_preview_widget_set_pdf_page(
|
||||||
|
widget, widget->pdf_page - 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preview_widget_on_next(GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
EvidencePreviewWidget *widget = data;
|
||||||
|
(void) button;
|
||||||
|
if (widget->pdf_page + 1U < widget->pdf_page_count)
|
||||||
|
evidence_preview_widget_set_pdf_page(
|
||||||
|
widget, widget->pdf_page + 1U);
|
||||||
|
}
|
||||||
|
|
||||||
EvidencePreviewWidget *evidence_preview_widget_new(
|
EvidencePreviewWidget *evidence_preview_widget_new(
|
||||||
TaskManager *task_manager,
|
TaskManager *task_manager,
|
||||||
EvidencePreviewWidgetSessionCheck session_check,
|
EvidencePreviewWidgetSessionCheck session_check,
|
||||||
|
|
@ -199,7 +487,10 @@ EvidencePreviewWidget *evidence_preview_widget_new(
|
||||||
widget->task_manager = task_manager;
|
widget->task_manager = task_manager;
|
||||||
widget->session_check = session_check;
|
widget->session_check = session_check;
|
||||||
widget->session_data = session_data;
|
widget->session_data = session_data;
|
||||||
|
widget->fit_mode = TRUE;
|
||||||
|
widget->zoom_index = 3;
|
||||||
widget->root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
widget->root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||||
|
gtk_widget_set_name(widget->root, "evidence-preview-widget");
|
||||||
g_object_weak_ref(G_OBJECT(widget->root),
|
g_object_weak_ref(G_OBJECT(widget->root),
|
||||||
preview_widget_root_finalized, widget);
|
preview_widget_root_finalized, widget);
|
||||||
widget->metadata = GTK_LABEL(gtk_label_new(""));
|
widget->metadata = GTK_LABEL(gtk_label_new(""));
|
||||||
|
|
@ -207,6 +498,65 @@ EvidencePreviewWidget *evidence_preview_widget_new(
|
||||||
gtk_label_set_selectable(widget->metadata, TRUE);
|
gtk_label_set_selectable(widget->metadata, TRUE);
|
||||||
widget->status = GTK_LABEL(gtk_label_new("Aucune preuve sélectionnée."));
|
widget->status = GTK_LABEL(gtk_label_new("Aucune preuve sélectionnée."));
|
||||||
gtk_label_set_xalign(widget->status, 0.0f);
|
gtk_label_set_xalign(widget->status, 0.0f);
|
||||||
|
widget->toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||||
|
gtk_widget_set_name(widget->toolbar, "evidence-preview-toolbar");
|
||||||
|
widget->zoom_out = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"zoom-out-symbolic"));
|
||||||
|
widget->fit = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"zoom-fit-best-symbolic"));
|
||||||
|
widget->zoom_in = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"zoom-in-symbolic"));
|
||||||
|
widget->previous_page = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"go-previous-symbolic"));
|
||||||
|
widget->next_page = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"go-next-symbolic"));
|
||||||
|
widget->zoom_label = GTK_LABEL(gtk_label_new("Ajusté"));
|
||||||
|
widget->page_label = GTK_LABEL(gtk_label_new("Page 0 / 0"));
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->zoom_out),
|
||||||
|
"evidence-preview-zoom-out");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->fit),
|
||||||
|
"evidence-preview-fit");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->zoom_in),
|
||||||
|
"evidence-preview-zoom-in");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->previous_page),
|
||||||
|
"evidence-preview-previous-page");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->next_page),
|
||||||
|
"evidence-preview-next-page");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->zoom_label),
|
||||||
|
"evidence-preview-zoom-label");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->page_label),
|
||||||
|
"evidence-preview-page-label");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(widget->zoom_out), "Zoom arrière");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(widget->fit),
|
||||||
|
"Ajuster à la fenêtre");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(widget->zoom_in), "Zoom avant");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(widget->previous_page),
|
||||||
|
"Page précédente");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(widget->next_page),
|
||||||
|
"Page suivante");
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar),
|
||||||
|
GTK_WIDGET(widget->zoom_out));
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar), GTK_WIDGET(widget->fit));
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar), GTK_WIDGET(widget->zoom_in));
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar), GTK_WIDGET(widget->zoom_label));
|
||||||
|
GtkWidget *spacer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
gtk_widget_set_hexpand(spacer, TRUE);
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar), spacer);
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar),
|
||||||
|
GTK_WIDGET(widget->previous_page));
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar), GTK_WIDGET(widget->page_label));
|
||||||
|
gtk_box_append(GTK_BOX(widget->toolbar),
|
||||||
|
GTK_WIDGET(widget->next_page));
|
||||||
|
g_signal_connect(widget->zoom_out, "clicked",
|
||||||
|
G_CALLBACK(preview_widget_on_zoom_out), widget);
|
||||||
|
g_signal_connect(widget->fit, "clicked",
|
||||||
|
G_CALLBACK(preview_widget_on_fit), widget);
|
||||||
|
g_signal_connect(widget->zoom_in, "clicked",
|
||||||
|
G_CALLBACK(preview_widget_on_zoom_in), widget);
|
||||||
|
g_signal_connect(widget->previous_page, "clicked",
|
||||||
|
G_CALLBACK(preview_widget_on_previous), widget);
|
||||||
|
g_signal_connect(widget->next_page, "clicked",
|
||||||
|
G_CALLBACK(preview_widget_on_next), widget);
|
||||||
widget->image = GTK_PICTURE(gtk_picture_new());
|
widget->image = GTK_PICTURE(gtk_picture_new());
|
||||||
widget->pdf = GTK_PICTURE(gtk_picture_new());
|
widget->pdf = GTK_PICTURE(gtk_picture_new());
|
||||||
widget->video = GTK_VIDEO(gtk_video_new());
|
widget->video = GTK_VIDEO(gtk_video_new());
|
||||||
|
|
@ -220,8 +570,25 @@ EvidencePreviewWidget *evidence_preview_widget_new(
|
||||||
GtkPicture *picture = i == 0 ? widget->image : widget->pdf;
|
GtkPicture *picture = i == 0 ? widget->image : widget->pdf;
|
||||||
gtk_picture_set_can_shrink(picture, TRUE);
|
gtk_picture_set_can_shrink(picture, TRUE);
|
||||||
gtk_picture_set_content_fit(picture, GTK_CONTENT_FIT_CONTAIN);
|
gtk_picture_set_content_fit(picture, GTK_CONTENT_FIT_CONTAIN);
|
||||||
gtk_widget_set_size_request(GTK_WIDGET(picture), 480, 260);
|
gtk_widget_set_hexpand(GTK_WIDGET(picture), TRUE);
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(picture), TRUE);
|
||||||
}
|
}
|
||||||
|
widget->image_scroll = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new());
|
||||||
|
widget->pdf_scroll = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new());
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->image_scroll),
|
||||||
|
"evidence-preview-image-scroll");
|
||||||
|
gtk_widget_set_name(GTK_WIDGET(widget->pdf_scroll),
|
||||||
|
"evidence-preview-pdf-scroll");
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(widget->image_scroll), 480, 260);
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(widget->pdf_scroll), 480, 260);
|
||||||
|
gtk_widget_set_hexpand(GTK_WIDGET(widget->image_scroll), TRUE);
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(widget->image_scroll), TRUE);
|
||||||
|
gtk_widget_set_hexpand(GTK_WIDGET(widget->pdf_scroll), TRUE);
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(widget->pdf_scroll), TRUE);
|
||||||
|
gtk_scrolled_window_set_child(widget->image_scroll,
|
||||||
|
GTK_WIDGET(widget->image));
|
||||||
|
gtk_scrolled_window_set_child(widget->pdf_scroll,
|
||||||
|
GTK_WIDGET(widget->pdf));
|
||||||
gtk_widget_set_size_request(GTK_WIDGET(widget->video), 480, 260);
|
gtk_widget_set_size_request(GTK_WIDGET(widget->video), 480, 260);
|
||||||
gtk_video_set_autoplay(widget->video, FALSE);
|
gtk_video_set_autoplay(widget->video, FALSE);
|
||||||
widget->video_controller = evidence_video_preview_controller_new(
|
widget->video_controller = evidence_video_preview_controller_new(
|
||||||
|
|
@ -251,11 +618,13 @@ EvidencePreviewWidget *evidence_preview_widget_new(
|
||||||
gtk_stack_add_named(widget->stack, gtk_label_new("Aucun aperçu."), "empty");
|
gtk_stack_add_named(widget->stack, gtk_label_new("Aucun aperçu."), "empty");
|
||||||
gtk_stack_add_named(widget->stack,
|
gtk_stack_add_named(widget->stack,
|
||||||
gtk_label_new("Chargement de l’aperçu…"), "loading");
|
gtk_label_new("Chargement de l’aperçu…"), "loading");
|
||||||
gtk_stack_add_named(widget->stack, GTK_WIDGET(widget->image), "image");
|
gtk_stack_add_named(widget->stack,
|
||||||
|
GTK_WIDGET(widget->image_scroll), "image");
|
||||||
gtk_stack_add_named(widget->stack, GTK_WIDGET(widget->video), "video");
|
gtk_stack_add_named(widget->stack, GTK_WIDGET(widget->video), "video");
|
||||||
gtk_stack_add_named(widget->stack, text_scroll, "text");
|
gtk_stack_add_named(widget->stack, text_scroll, "text");
|
||||||
gtk_stack_add_named(widget->stack, email_scroll, "email");
|
gtk_stack_add_named(widget->stack, email_scroll, "email");
|
||||||
gtk_stack_add_named(widget->stack, GTK_WIDGET(widget->pdf), "pdf");
|
gtk_stack_add_named(widget->stack,
|
||||||
|
GTK_WIDGET(widget->pdf_scroll), "pdf");
|
||||||
gtk_stack_add_named(widget->stack,
|
gtk_stack_add_named(widget->stack,
|
||||||
gtk_label_new("Format reconnu mais aperçu indisponible."),
|
gtk_label_new("Format reconnu mais aperçu indisponible."),
|
||||||
"unsupported");
|
"unsupported");
|
||||||
|
|
@ -264,7 +633,10 @@ EvidencePreviewWidget *evidence_preview_widget_new(
|
||||||
gtk_stack_set_visible_child_name(widget->stack, "empty");
|
gtk_stack_set_visible_child_name(widget->stack, "empty");
|
||||||
gtk_box_append(GTK_BOX(widget->root), GTK_WIDGET(widget->metadata));
|
gtk_box_append(GTK_BOX(widget->root), GTK_WIDGET(widget->metadata));
|
||||||
gtk_box_append(GTK_BOX(widget->root), GTK_WIDGET(widget->status));
|
gtk_box_append(GTK_BOX(widget->root), GTK_WIDGET(widget->status));
|
||||||
|
gtk_box_append(GTK_BOX(widget->root), widget->toolbar);
|
||||||
gtk_box_append(GTK_BOX(widget->root), GTK_WIDGET(widget->stack));
|
gtk_box_append(GTK_BOX(widget->root), GTK_WIDGET(widget->stack));
|
||||||
|
preview_widget_apply_zoom(widget);
|
||||||
|
preview_widget_update_page_controls(widget);
|
||||||
g_object_set_data(G_OBJECT(widget->root),
|
g_object_set_data(G_OBJECT(widget->root),
|
||||||
"evidence-preview-widget", widget);
|
"evidence-preview-widget", widget);
|
||||||
return widget;
|
return widget;
|
||||||
|
|
@ -277,35 +649,23 @@ GtkWidget *evidence_preview_widget_get_widget(
|
||||||
void evidence_preview_widget_show(EvidencePreviewWidget *widget,
|
void evidence_preview_widget_show(EvidencePreviewWidget *widget,
|
||||||
const EvidencePreviewRequest *request, const char *metadata)
|
const EvidencePreviewRequest *request, const char *metadata)
|
||||||
{
|
{
|
||||||
EvidencePreviewRequest *owned;
|
|
||||||
PreviewWidgetContext *context;
|
|
||||||
GError *error = NULL;
|
|
||||||
if (widget == NULL || request == NULL) return;
|
if (widget == NULL || request == NULL) return;
|
||||||
evidence_preview_widget_cancel(widget);
|
evidence_preview_widget_cancel(widget);
|
||||||
widget->identifier = g_strdup(request->evidence_identifier);
|
widget->identifier = g_strdup(request->evidence_identifier);
|
||||||
owned = evidence_preview_request_new(request->investigation_root_path,
|
widget->request = evidence_preview_request_new(
|
||||||
|
request->investigation_root_path,
|
||||||
request->evidence_identifier, request->relative_path,
|
request->evidence_identifier, request->relative_path,
|
||||||
request->expected_sha256, request->mime_type, widget->generation);
|
request->expected_sha256, request->mime_type, widget->generation);
|
||||||
gtk_label_set_text(widget->metadata, metadata != NULL ? metadata : "");
|
widget->metadata_text = g_strdup(metadata);
|
||||||
gtk_label_set_text(widget->status,
|
widget->fit_mode = TRUE;
|
||||||
"Détection, vérification et chargement en cours…");
|
widget->zoom_index = 3;
|
||||||
gtk_stack_set_visible_child_name(widget->stack, "loading");
|
widget->raster_available = FALSE;
|
||||||
context = g_new0(PreviewWidgetContext, 1);
|
widget->pdf_available = FALSE;
|
||||||
g_weak_ref_init(&context->root, G_OBJECT(widget->root));
|
widget->pdf_page = request->pdf_page;
|
||||||
context->identifier = g_strdup(widget->identifier);
|
widget->pdf_page_count = 0;
|
||||||
context->generation = widget->generation;
|
preview_widget_apply_zoom(widget);
|
||||||
if (owned != NULL)
|
preview_widget_update_page_controls(widget);
|
||||||
widget->task = evidence_preview_task_start(widget->task_manager,
|
preview_widget_start_request(widget, request->pdf_page);
|
||||||
owned, preview_widget_completed, context,
|
|
||||||
preview_widget_context_free, &error);
|
|
||||||
evidence_preview_request_free(owned);
|
|
||||||
if (widget->task == NULL) {
|
|
||||||
preview_widget_context_free(context);
|
|
||||||
gtk_stack_set_visible_child_name(widget->stack, "error");
|
|
||||||
gtk_label_set_text(widget->status,
|
|
||||||
error != NULL ? error->message : "Aperçu impossible.");
|
|
||||||
}
|
|
||||||
g_clear_error(&error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void evidence_preview_widget_free(EvidencePreviewWidget *widget)
|
void evidence_preview_widget_free(EvidencePreviewWidget *widget)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
struct OcrProvenanceOverlay {
|
struct OcrProvenanceOverlay {
|
||||||
GtkWidget *root;
|
GtkWidget *root;
|
||||||
|
GtkWidget *layer;
|
||||||
|
GtkScrolledWindow *scroll;
|
||||||
GtkPicture *picture;
|
GtkPicture *picture;
|
||||||
GtkDrawingArea *drawing;
|
GtkDrawingArea *drawing;
|
||||||
GtkLabel *details;
|
GtkLabel *details;
|
||||||
|
|
@ -10,6 +12,19 @@ struct OcrProvenanceOverlay {
|
||||||
IdentitySourceBox box;
|
IdentitySourceBox box;
|
||||||
gboolean has_box;
|
gboolean has_box;
|
||||||
guint64 generation;
|
guint64 generation;
|
||||||
|
guint page;
|
||||||
|
guint zoom_index;
|
||||||
|
gboolean fit_mode;
|
||||||
|
gint image_width;
|
||||||
|
gint image_height;
|
||||||
|
GtkLabel *zoom_label;
|
||||||
|
GtkButton *zoom_out;
|
||||||
|
GtkButton *fit;
|
||||||
|
GtkButton *zoom_in;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const double provenance_zoom_factors[] = {
|
||||||
|
0.25, 0.50, 0.75, 1.00, 1.25, 1.50, 2.00, 3.00, 4.00
|
||||||
};
|
};
|
||||||
|
|
||||||
static void draw_region(GtkDrawingArea *area, cairo_t *cr, int width,
|
static void draw_region(GtkDrawingArea *area, cairo_t *cr, int width,
|
||||||
|
|
@ -36,22 +51,119 @@ OcrProvenanceOverlay *ocr_provenance_overlay_new(void)
|
||||||
{
|
{
|
||||||
OcrProvenanceOverlay *overlay = g_new0(OcrProvenanceOverlay, 1);
|
OcrProvenanceOverlay *overlay = g_new0(OcrProvenanceOverlay, 1);
|
||||||
overlay->root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
|
overlay->root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
|
||||||
GtkWidget *layer = gtk_overlay_new();
|
overlay->fit_mode = TRUE;
|
||||||
|
overlay->zoom_index = 3;
|
||||||
|
GtkWidget *toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||||
|
overlay->zoom_out = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"zoom-out-symbolic"));
|
||||||
|
overlay->fit = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"zoom-fit-best-symbolic"));
|
||||||
|
overlay->zoom_in = GTK_BUTTON(gtk_button_new_from_icon_name(
|
||||||
|
"zoom-in-symbolic"));
|
||||||
|
overlay->zoom_label = GTK_LABEL(gtk_label_new("Ajusté"));
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(overlay->zoom_out), "Zoom arrière");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(overlay->fit),
|
||||||
|
"Ajuster à la fenêtre");
|
||||||
|
gtk_widget_set_tooltip_text(GTK_WIDGET(overlay->zoom_in), "Zoom avant");
|
||||||
|
gtk_box_append(GTK_BOX(toolbar), GTK_WIDGET(overlay->zoom_out));
|
||||||
|
gtk_box_append(GTK_BOX(toolbar), GTK_WIDGET(overlay->fit));
|
||||||
|
gtk_box_append(GTK_BOX(toolbar), GTK_WIDGET(overlay->zoom_in));
|
||||||
|
gtk_box_append(GTK_BOX(toolbar), GTK_WIDGET(overlay->zoom_label));
|
||||||
|
overlay->layer = gtk_overlay_new();
|
||||||
overlay->picture = GTK_PICTURE(gtk_picture_new());
|
overlay->picture = GTK_PICTURE(gtk_picture_new());
|
||||||
gtk_picture_set_content_fit(overlay->picture, GTK_CONTENT_FIT_CONTAIN);
|
gtk_picture_set_content_fit(overlay->picture, GTK_CONTENT_FIT_CONTAIN);
|
||||||
overlay->drawing = GTK_DRAWING_AREA(gtk_drawing_area_new());
|
overlay->drawing = GTK_DRAWING_AREA(gtk_drawing_area_new());
|
||||||
gtk_drawing_area_set_draw_func(overlay->drawing, draw_region, overlay,
|
gtk_drawing_area_set_draw_func(overlay->drawing, draw_region, overlay,
|
||||||
NULL);
|
NULL);
|
||||||
gtk_overlay_set_child(GTK_OVERLAY(layer), GTK_WIDGET(overlay->picture));
|
gtk_overlay_set_child(GTK_OVERLAY(overlay->layer),
|
||||||
gtk_overlay_add_overlay(GTK_OVERLAY(layer), GTK_WIDGET(overlay->drawing));
|
GTK_WIDGET(overlay->picture));
|
||||||
gtk_widget_set_size_request(layer, 360, 220);
|
gtk_overlay_add_overlay(GTK_OVERLAY(overlay->layer),
|
||||||
|
GTK_WIDGET(overlay->drawing));
|
||||||
|
overlay->scroll = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new());
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(overlay->scroll), 360, 220);
|
||||||
|
gtk_scrolled_window_set_child(overlay->scroll, overlay->layer);
|
||||||
overlay->details = GTK_LABEL(gtk_label_new("Zone source indisponible"));
|
overlay->details = GTK_LABEL(gtk_label_new("Zone source indisponible"));
|
||||||
gtk_label_set_wrap(overlay->details, TRUE);
|
gtk_label_set_wrap(overlay->details, TRUE);
|
||||||
gtk_box_append(GTK_BOX(overlay->root), layer);
|
gtk_box_append(GTK_BOX(overlay->root), toolbar);
|
||||||
|
gtk_box_append(GTK_BOX(overlay->root), GTK_WIDGET(overlay->scroll));
|
||||||
gtk_box_append(GTK_BOX(overlay->root), GTK_WIDGET(overlay->details));
|
gtk_box_append(GTK_BOX(overlay->root), GTK_WIDGET(overlay->details));
|
||||||
|
g_signal_connect_swapped(overlay->zoom_out, "clicked",
|
||||||
|
G_CALLBACK(ocr_provenance_overlay_zoom_out), overlay);
|
||||||
|
g_signal_connect_swapped(overlay->fit, "clicked",
|
||||||
|
G_CALLBACK(ocr_provenance_overlay_fit), overlay);
|
||||||
|
g_signal_connect_swapped(overlay->zoom_in, "clicked",
|
||||||
|
G_CALLBACK(ocr_provenance_overlay_zoom_in), overlay);
|
||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void provenance_apply_zoom(OcrProvenanceOverlay *overlay)
|
||||||
|
{
|
||||||
|
double factor = provenance_zoom_factors[overlay->zoom_index];
|
||||||
|
char *label = overlay->fit_mode ? g_strdup("Ajusté") :
|
||||||
|
g_strdup_printf("%.0f %%", factor * 100.0);
|
||||||
|
gtk_label_set_text(overlay->zoom_label, label);
|
||||||
|
g_free(label);
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(overlay->zoom_out),
|
||||||
|
overlay->fit_mode || overlay->zoom_index > 0);
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(overlay->zoom_in),
|
||||||
|
overlay->fit_mode || overlay->zoom_index + 1U <
|
||||||
|
G_N_ELEMENTS(provenance_zoom_factors));
|
||||||
|
gtk_widget_set_sensitive(GTK_WIDGET(overlay->fit), !overlay->fit_mode);
|
||||||
|
if (overlay->fit_mode) {
|
||||||
|
gtk_widget_set_size_request(overlay->layer, -1, -1);
|
||||||
|
gtk_widget_set_hexpand(overlay->layer, TRUE);
|
||||||
|
gtk_widget_set_vexpand(overlay->layer, TRUE);
|
||||||
|
gtk_scrolled_window_set_policy(overlay->scroll,
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_NEVER);
|
||||||
|
} else {
|
||||||
|
gtk_widget_set_hexpand(overlay->layer, FALSE);
|
||||||
|
gtk_widget_set_vexpand(overlay->layer, FALSE);
|
||||||
|
gtk_widget_set_size_request(overlay->layer,
|
||||||
|
MAX(1, (gint) (overlay->image_width * factor)),
|
||||||
|
MAX(1, (gint) (overlay->image_height * factor)));
|
||||||
|
gtk_scrolled_window_set_policy(overlay->scroll,
|
||||||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
}
|
||||||
|
gtk_widget_queue_draw(GTK_WIDGET(overlay->drawing));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ocr_provenance_overlay_zoom_in(OcrProvenanceOverlay *overlay)
|
||||||
|
{
|
||||||
|
if (overlay == NULL || overlay->texture == NULL) return;
|
||||||
|
if (overlay->fit_mode) {
|
||||||
|
overlay->fit_mode = FALSE;
|
||||||
|
overlay->zoom_index = 4;
|
||||||
|
} else if (overlay->zoom_index + 1U <
|
||||||
|
G_N_ELEMENTS(provenance_zoom_factors))
|
||||||
|
overlay->zoom_index++;
|
||||||
|
provenance_apply_zoom(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ocr_provenance_overlay_zoom_out(OcrProvenanceOverlay *overlay)
|
||||||
|
{
|
||||||
|
if (overlay == NULL || overlay->texture == NULL) return;
|
||||||
|
if (overlay->fit_mode) {
|
||||||
|
overlay->fit_mode = FALSE;
|
||||||
|
overlay->zoom_index = 2;
|
||||||
|
} else if (overlay->zoom_index > 0)
|
||||||
|
overlay->zoom_index--;
|
||||||
|
provenance_apply_zoom(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ocr_provenance_overlay_fit(OcrProvenanceOverlay *overlay)
|
||||||
|
{
|
||||||
|
if (overlay == NULL || overlay->texture == NULL) return;
|
||||||
|
overlay->fit_mode = TRUE;
|
||||||
|
provenance_apply_zoom(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ocr_provenance_overlay_get_zoom(
|
||||||
|
const OcrProvenanceOverlay *overlay)
|
||||||
|
{
|
||||||
|
return overlay != NULL && !overlay->fit_mode
|
||||||
|
? provenance_zoom_factors[overlay->zoom_index] : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
void ocr_provenance_overlay_free(OcrProvenanceOverlay *overlay)
|
void ocr_provenance_overlay_free(OcrProvenanceOverlay *overlay)
|
||||||
{
|
{
|
||||||
if (overlay == NULL) return;
|
if (overlay == NULL) return;
|
||||||
|
|
@ -72,8 +184,15 @@ void ocr_provenance_overlay_set_image(OcrProvenanceOverlay *overlay,
|
||||||
g_clear_object(&overlay->texture);
|
g_clear_object(&overlay->texture);
|
||||||
overlay->texture = bytes != NULL ? gdk_texture_new_from_bytes(bytes, NULL)
|
overlay->texture = bytes != NULL ? gdk_texture_new_from_bytes(bytes, NULL)
|
||||||
: NULL;
|
: NULL;
|
||||||
|
overlay->image_width = overlay->texture != NULL
|
||||||
|
? gdk_texture_get_width(overlay->texture) : 0;
|
||||||
|
overlay->image_height = overlay->texture != NULL
|
||||||
|
? gdk_texture_get_height(overlay->texture) : 0;
|
||||||
gtk_picture_set_paintable(overlay->picture,
|
gtk_picture_set_paintable(overlay->picture,
|
||||||
overlay->texture != NULL ? GDK_PAINTABLE(overlay->texture) : NULL);
|
overlay->texture != NULL ? GDK_PAINTABLE(overlay->texture) : NULL);
|
||||||
|
overlay->fit_mode = TRUE;
|
||||||
|
overlay->zoom_index = 3;
|
||||||
|
provenance_apply_zoom(overlay);
|
||||||
gtk_widget_queue_draw(GTK_WIDGET(overlay->drawing));
|
gtk_widget_queue_draw(GTK_WIDGET(overlay->drawing));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +203,8 @@ void ocr_provenance_overlay_set_field(OcrProvenanceOverlay *overlay,
|
||||||
overlay->generation = generation;
|
overlay->generation = generation;
|
||||||
const IdentitySourceBox *box = field != NULL
|
const IdentitySourceBox *box = field != NULL
|
||||||
? identity_field_observation_get_box(field) : NULL;
|
? identity_field_observation_get_box(field) : NULL;
|
||||||
overlay->has_box = box != NULL && box->available;
|
overlay->has_box = box != NULL && box->available &&
|
||||||
|
(overlay->page == 0 || box->page == (gint) overlay->page);
|
||||||
if (overlay->has_box) overlay->box = *box;
|
if (overlay->has_box) overlay->box = *box;
|
||||||
char *text = overlay->has_box
|
char *text = overlay->has_box
|
||||||
? g_strdup_printf("%s — confiance %.1f — page %d — "
|
? g_strdup_printf("%s — confiance %.1f — page %d — "
|
||||||
|
|
@ -99,6 +219,20 @@ void ocr_provenance_overlay_set_field(OcrProvenanceOverlay *overlay,
|
||||||
gtk_widget_queue_draw(GTK_WIDGET(overlay->drawing));
|
gtk_widget_queue_draw(GTK_WIDGET(overlay->drawing));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ocr_provenance_overlay_set_page(
|
||||||
|
OcrProvenanceOverlay *overlay, guint page, guint64 generation)
|
||||||
|
{
|
||||||
|
if (overlay == NULL || generation < overlay->generation) return;
|
||||||
|
overlay->generation = generation;
|
||||||
|
overlay->page = page;
|
||||||
|
if (overlay->has_box && overlay->box.page != (gint) page) {
|
||||||
|
overlay->has_box = FALSE;
|
||||||
|
gtk_label_set_text(overlay->details,
|
||||||
|
"Zone source indisponible sur cette page");
|
||||||
|
gtk_widget_queue_draw(GTK_WIDGET(overlay->drawing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ocr_provenance_overlay_clear(OcrProvenanceOverlay *overlay,
|
void ocr_provenance_overlay_clear(OcrProvenanceOverlay *overlay,
|
||||||
guint64 generation)
|
guint64 generation)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1183,6 +1183,8 @@ static void workspace_render_identity_ocr(Workspace *workspace)
|
||||||
WorkspaceIdentityOcrRecord *record = g_ptr_array_index(
|
WorkspaceIdentityOcrRecord *record = g_ptr_array_index(
|
||||||
workspace->identity_ocr_records, selected);
|
workspace->identity_ocr_records, selected);
|
||||||
IdentityOcrRun *run = record->run;
|
IdentityOcrRun *run = record->run;
|
||||||
|
ocr_provenance_overlay_set_page(workspace->identity_ocr_overlay,
|
||||||
|
identity_ocr_run_get_page(run), selected + 1U);
|
||||||
char *summary = g_strdup_printf(
|
char *summary = g_strdup_printf(
|
||||||
"Run actif : %s\nUTC : %s\nPersonne liée : %s\n"
|
"Run actif : %s\nUTC : %s\nPersonne liée : %s\n"
|
||||||
"Document : %s — %s — page %u\nLangues : %s\n"
|
"Document : %s — %s — page %u\nLangues : %s\n"
|
||||||
|
|
|
||||||
|
|
@ -47,27 +47,6 @@ static GtkWidget *find_entry(GtkWidget *widget, const char *placeholder)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkDropDown *find_evidence_drop_down(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 matches = first != NULL && g_strcmp0(
|
|
||||||
gtk_string_object_get_string(first),
|
|
||||||
"Aucune preuve associée") == 0;
|
|
||||||
g_clear_object(&first);
|
|
||||||
if (matches) 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_drop_down(child);
|
|
||||||
if (match != NULL) return match;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GtkDropDown *find_drop_down_starting_with(GtkWidget *widget,
|
static GtkDropDown *find_drop_down_starting_with(GtkWidget *widget,
|
||||||
const char *first_label)
|
const char *first_label)
|
||||||
{
|
{
|
||||||
|
|
@ -89,55 +68,50 @@ static GtkDropDown *find_drop_down_starting_with(GtkWidget *widget,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *find_first_button(GtkWidget *widget)
|
static GtkWidget *find_named(GtkWidget *widget, const char *name)
|
||||||
{
|
{
|
||||||
if (GTK_IS_BUTTON(widget)) return widget;
|
if (g_strcmp0(gtk_widget_get_name(widget), name) == 0)
|
||||||
|
return widget;
|
||||||
for (GtkWidget *child = gtk_widget_get_first_child(widget);
|
for (GtkWidget *child = gtk_widget_get_first_child(widget);
|
||||||
child != NULL; child = gtk_widget_get_next_sibling(child)) {
|
child != NULL; child = gtk_widget_get_next_sibling(child)) {
|
||||||
GtkWidget *match = find_first_button(child);
|
GtkWidget *match = find_named(child, name);
|
||||||
if (match != NULL) return match;
|
if (match != NULL) return match;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkListView *find_list_view(GtkWidget *widget, gboolean mapped_only)
|
static gboolean frame_seen(GtkWidget *widget, GdkFrameClock *clock,
|
||||||
|
gpointer data)
|
||||||
{
|
{
|
||||||
if (GTK_IS_LIST_VIEW(widget) &&
|
gboolean *seen = data;
|
||||||
(!mapped_only || gtk_widget_get_mapped(widget)))
|
(void) widget;
|
||||||
return GTK_LIST_VIEW(widget);
|
(void) clock;
|
||||||
for (GtkWidget *child = gtk_widget_get_first_child(widget);
|
*seen = TRUE;
|
||||||
child != NULL; child = gtk_widget_get_next_sibling(child)) {
|
return G_SOURCE_REMOVE;
|
||||||
GtkListView *match = find_list_view(child, mapped_only);
|
|
||||||
if (match != NULL) return match;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkListView *find_popup_list_view(GtkDropDown *dropdown)
|
static void wait_for_frame(GtkWidget *widget)
|
||||||
{
|
{
|
||||||
GtkWidget *root = GTK_WIDGET(gtk_widget_get_root(GTK_WIDGET(dropdown)));
|
gboolean seen = FALSE;
|
||||||
GtkListView *list_view;
|
gtk_widget_add_tick_callback(widget, frame_seen, &seen, NULL);
|
||||||
GtkWidget *button = find_first_button(GTK_WIDGET(dropdown));
|
while (!seen)
|
||||||
g_assert_nonnull(button);
|
g_main_context_iteration(NULL, TRUE);
|
||||||
if (GTK_IS_TOGGLE_BUTTON(button))
|
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
|
|
||||||
else
|
|
||||||
gtk_widget_activate(button);
|
|
||||||
while (g_main_context_iteration(NULL, FALSE)) {}
|
|
||||||
list_view = find_list_view(GTK_WIDGET(dropdown), TRUE);
|
|
||||||
if (list_view == NULL && root != NULL)
|
|
||||||
list_view = find_list_view(root, TRUE);
|
|
||||||
return list_view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void activate_popup_item(GtkListView *list_view, guint position)
|
static void select_drop_down(GtkDropDown *dropdown, guint position)
|
||||||
{
|
{
|
||||||
GtkSelectionModel *selection = gtk_list_view_get_model(list_view);
|
g_test_message("sélecteur=%s position=%u mapped=%d visible=%d",
|
||||||
g_assert_nonnull(selection);
|
gtk_widget_get_name(GTK_WIDGET(dropdown)), position,
|
||||||
g_assert_true(gtk_selection_model_select_item(
|
gtk_widget_get_mapped(GTK_WIDGET(dropdown)),
|
||||||
selection, position, TRUE));
|
gtk_widget_get_visible(GTK_WIDGET(dropdown)));
|
||||||
g_signal_emit_by_name(list_view, "activate", position);
|
g_assert_true(gtk_widget_get_mapped(GTK_WIDGET(dropdown)));
|
||||||
while (g_main_context_iteration(NULL, FALSE)) {}
|
g_assert_true(gtk_widget_activate(GTK_WIDGET(dropdown)));
|
||||||
|
wait_for_frame(GTK_WIDGET(dropdown));
|
||||||
|
g_assert_true(gtk_widget_activate(GTK_WIDGET(dropdown)));
|
||||||
|
wait_for_frame(GTK_WIDGET(dropdown));
|
||||||
|
gtk_drop_down_set_selected(dropdown, position);
|
||||||
|
g_main_context_iteration(NULL, FALSE);
|
||||||
|
g_assert_cmpuint(gtk_drop_down_get_selected(dropdown), ==, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *find_label_containing(GtkWidget *widget, const char *text)
|
static GtkWidget *find_label_containing(GtkWidget *widget, const char *text)
|
||||||
|
|
@ -183,6 +157,12 @@ static gboolean click_cancel(gpointer data)
|
||||||
GtkWidget *button, *designation, *search;
|
GtkWidget *button, *designation, *search;
|
||||||
GtkDropDown *evidence, *retained_type, *type_filter;
|
GtkDropDown *evidence, *retained_type, *type_filter;
|
||||||
g_assert_nonnull(dialog);
|
g_assert_nonnull(dialog);
|
||||||
|
g_assert_true(gtk_window_get_transient_for(dialog) ==
|
||||||
|
context->main_window);
|
||||||
|
g_assert_true(gtk_window_get_modal(dialog));
|
||||||
|
g_assert_true(gtk_window_get_destroy_with_parent(dialog));
|
||||||
|
gtk_window_set_default_size(dialog, 760, 560);
|
||||||
|
wait_for_frame(GTK_WIDGET(dialog));
|
||||||
designation = find_entry(GTK_WIDGET(dialog),
|
designation = find_entry(GTK_WIDGET(dialog),
|
||||||
"Personne présumée liée aux comptes");
|
"Personne présumée liée aux comptes");
|
||||||
g_assert_nonnull(designation);
|
g_assert_nonnull(designation);
|
||||||
|
|
@ -190,41 +170,32 @@ static gboolean click_cancel(gpointer data)
|
||||||
button = find_button(GTK_WIDGET(dialog), "Suivant");
|
button = find_button(GTK_WIDGET(dialog), "Suivant");
|
||||||
g_assert_nonnull(button);
|
g_assert_nonnull(button);
|
||||||
g_signal_emit_by_name(button, "clicked");
|
g_signal_emit_by_name(button, "clicked");
|
||||||
|
wait_for_frame(GTK_WIDGET(button));
|
||||||
g_signal_emit_by_name(button, "clicked");
|
g_signal_emit_by_name(button, "clicked");
|
||||||
|
wait_for_frame(GTK_WIDGET(button));
|
||||||
search = find_entry(GTK_WIDGET(dialog),
|
search = find_entry(GTK_WIDGET(dialog),
|
||||||
"Rechercher par nom, description ou type");
|
"Rechercher par nom, description ou type");
|
||||||
g_assert_nonnull(search);
|
g_assert_nonnull(search);
|
||||||
evidence = find_evidence_drop_down(GTK_WIDGET(dialog));
|
evidence = GTK_DROP_DOWN(find_named(GTK_WIDGET(dialog),
|
||||||
|
"create-person-evidence-dropdown"));
|
||||||
g_assert_nonnull(evidence);
|
g_assert_nonnull(evidence);
|
||||||
context->evidence_model = gtk_drop_down_get_model(evidence);
|
context->evidence_model = gtk_drop_down_get_model(evidence);
|
||||||
g_assert_nonnull(context->evidence_model);
|
g_assert_nonnull(context->evidence_model);
|
||||||
g_object_weak_ref(G_OBJECT(context->evidence_model),
|
g_object_weak_ref(G_OBJECT(context->evidence_model),
|
||||||
evidence_model_destroyed, context);
|
evidence_model_destroyed, context);
|
||||||
GtkListView *popup_list = find_popup_list_view(evidence);
|
select_drop_down(evidence, 1);
|
||||||
g_assert_nonnull(popup_list);
|
select_drop_down(evidence, 2);
|
||||||
activate_popup_item(popup_list, 1);
|
select_drop_down(evidence, 0);
|
||||||
popup_list = find_popup_list_view(evidence);
|
select_drop_down(evidence, 1);
|
||||||
g_assert_nonnull(popup_list);
|
|
||||||
activate_popup_item(popup_list, 2);
|
|
||||||
popup_list = find_popup_list_view(evidence);
|
|
||||||
g_assert_nonnull(popup_list);
|
|
||||||
activate_popup_item(popup_list, 0);
|
|
||||||
popup_list = find_popup_list_view(evidence);
|
|
||||||
g_assert_nonnull(popup_list);
|
|
||||||
activate_popup_item(popup_list, 1);
|
|
||||||
gtk_editable_set_text(GTK_EDITABLE(search), "recherche");
|
gtk_editable_set_text(GTK_EDITABLE(search), "recherche");
|
||||||
g_assert_true(gtk_drop_down_get_model(evidence) ==
|
g_assert_true(gtk_drop_down_get_model(evidence) ==
|
||||||
context->evidence_model);
|
context->evidence_model);
|
||||||
g_assert_false(context->evidence_model_destroyed);
|
g_assert_false(context->evidence_model_destroyed);
|
||||||
type_filter = find_drop_down_starting_with(
|
type_filter = GTK_DROP_DOWN(find_named(GTK_WIDGET(dialog),
|
||||||
GTK_WIDGET(dialog), "Tous les types");
|
"create-person-evidence-type-filter"));
|
||||||
g_assert_nonnull(type_filter);
|
g_assert_nonnull(type_filter);
|
||||||
popup_list = find_popup_list_view(type_filter);
|
select_drop_down(type_filter, 2);
|
||||||
g_assert_nonnull(popup_list);
|
select_drop_down(evidence, 1);
|
||||||
activate_popup_item(popup_list, 2);
|
|
||||||
popup_list = find_popup_list_view(evidence);
|
|
||||||
g_assert_nonnull(popup_list);
|
|
||||||
activate_popup_item(popup_list, 1);
|
|
||||||
g_assert_cmpuint(gtk_drop_down_get_selected(evidence), ==, 1);
|
g_assert_cmpuint(gtk_drop_down_get_selected(evidence), ==, 1);
|
||||||
button = find_button(GTK_WIDGET(dialog), "Ajouter à la sélection");
|
button = find_button(GTK_WIDGET(dialog), "Ajouter à la sélection");
|
||||||
g_assert_nonnull(button);
|
g_assert_nonnull(button);
|
||||||
|
|
@ -236,15 +207,11 @@ static gboolean click_cancel(gpointer data)
|
||||||
g_assert_nonnull(find_label_containing(GTK_WIDGET(dialog),
|
g_assert_nonnull(find_label_containing(GTK_WIDGET(dialog),
|
||||||
"Type : email (email)"));
|
"Type : email (email)"));
|
||||||
g_assert_nonnull(find_label_containing(GTK_WIDGET(dialog), "— email"));
|
g_assert_nonnull(find_label_containing(GTK_WIDGET(dialog), "— email"));
|
||||||
popup_list = find_popup_list_view(type_filter);
|
select_drop_down(type_filter, 0);
|
||||||
g_assert_nonnull(popup_list);
|
|
||||||
activate_popup_item(popup_list, 0);
|
|
||||||
gtk_editable_set_text(GTK_EDITABLE(search), "AUTRE");
|
gtk_editable_set_text(GTK_EDITABLE(search), "AUTRE");
|
||||||
g_assert_true(gtk_drop_down_get_model(evidence) ==
|
g_assert_true(gtk_drop_down_get_model(evidence) ==
|
||||||
context->evidence_model);
|
context->evidence_model);
|
||||||
popup_list = find_popup_list_view(evidence);
|
select_drop_down(evidence, 1);
|
||||||
g_assert_nonnull(popup_list);
|
|
||||||
activate_popup_item(popup_list, 1);
|
|
||||||
button = find_button(GTK_WIDGET(dialog), "Ajouter à la sélection");
|
button = find_button(GTK_WIDGET(dialog), "Ajouter à la sélection");
|
||||||
g_assert_nonnull(button);
|
g_assert_nonnull(button);
|
||||||
g_signal_emit_by_name(button, "clicked");
|
g_signal_emit_by_name(button, "clicked");
|
||||||
|
|
@ -317,6 +284,8 @@ int main(int argc, char **argv)
|
||||||
g_print("SKIP: aucun affichage GTK disponible.\n");
|
g_print("SKIP: aucun affichage GTK disponible.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
g_object_set(gtk_settings_get_default(),
|
||||||
|
"gtk-enable-animations", FALSE, NULL);
|
||||||
context.application = gtk_application_new(
|
context.application = gtk_application_new(
|
||||||
"org.labfy.Investigation.DialogTest",
|
"org.labfy.Investigation.DialogTest",
|
||||||
G_APPLICATION_NON_UNIQUE);
|
G_APPLICATION_NON_UNIQUE);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ typedef struct {
|
||||||
guint guard;
|
guint guard;
|
||||||
guint completion_count;
|
guint completion_count;
|
||||||
guint layout_index;
|
guint layout_index;
|
||||||
|
guint confirmation_stage;
|
||||||
|
int confirmation_width;
|
||||||
|
int confirmation_height;
|
||||||
gboolean layout_resize_pending;
|
gboolean layout_resize_pending;
|
||||||
gboolean passed;
|
gboolean passed;
|
||||||
gboolean persisted;
|
gboolean persisted;
|
||||||
|
|
@ -284,12 +287,26 @@ static gboolean drive(gpointer data)
|
||||||
g_assert_cmpstr(gtk_stack_get_visible_child_name(transcription),
|
g_assert_cmpstr(gtk_stack_get_visible_child_name(transcription),
|
||||||
==,"raw");
|
==,"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)
|
if(find_button(root,"Analyser comme document d’identité")==NULL)
|
||||||
return G_SOURCE_CONTINUE;
|
return G_SOURCE_CONTINUE;
|
||||||
if(!gtk_widget_get_sensitive(find_button(root,
|
if(!gtk_widget_get_sensitive(find_button(root,
|
||||||
"Analyser comme document d’identité")))return G_SOURCE_CONTINUE;
|
"Analyser comme document d’identité")))return G_SOURCE_CONTINUE;
|
||||||
g_signal_emit_by_name(find_button(root,
|
g_signal_emit_by_name(find_button(root,
|
||||||
"Analyser comme document d’identité"),"clicked");
|
"Analyser comme document d’identité"),"clicked");
|
||||||
|
g_assert_nonnull(find_label(root,"OCR contrôlé en cours"));
|
||||||
context->phase++;break;
|
context->phase++;break;
|
||||||
case 1:
|
case 1:
|
||||||
if(find_label(root,"OCR terminé")==NULL)return G_SOURCE_CONTINUE;
|
if(find_label(root,"OCR terminé")==NULL)return G_SOURCE_CONTINUE;
|
||||||
|
|
@ -406,6 +423,60 @@ static gboolean drive(gpointer data)
|
||||||
"Analyser comme document d’identité"),"clicked");
|
"Analyser comme document d’identité"),"clicked");
|
||||||
context->phase++;break;
|
context->phase++;break;
|
||||||
case 3:
|
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;
|
if(find_label(root,"OCR terminé")==NULL)return G_SOURCE_CONTINUE;
|
||||||
{
|
{
|
||||||
GtkStack *transcription=GTK_STACK(find_named(root,
|
GtkStack *transcription=GTK_STACK(find_named(root,
|
||||||
|
|
@ -492,14 +563,16 @@ static gboolean drive(gpointer data)
|
||||||
button=g_object_get_data(
|
button=g_object_get_data(
|
||||||
G_OBJECT(pdf_third_row),"identity-reject-button");
|
G_OBJECT(pdf_third_row),"identity-reject-button");
|
||||||
g_assert_nonnull(button);g_signal_emit_by_name(button,"clicked");
|
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_signal_emit_by_name(find_button(root,"Suivant"),"clicked");
|
||||||
g_assert_nonnull(find_label(root,"page 2"));
|
g_assert_nonnull(find_label(root,"page 2"));
|
||||||
g_assert_nonnull(find_label(root,"PAGE 2 VALEUR FINALE"));
|
g_assert_nonnull(find_label(root,"PAGE 2 VALEUR FINALE"));
|
||||||
g_assert_nonnull(find_label(root,"authenticité n’est pas établie"));
|
g_assert_nonnull(find_label(root,"authenticité n’est pas établie"));
|
||||||
g_assert_null(find_label(root,"brut : PAGE2"));
|
g_assert_null(find_label(root,"brut : PAGE2"));
|
||||||
g_signal_emit_by_name(find_button(root,
|
context->confirmation_stage=1;break;
|
||||||
"Créer la personne"),"clicked");
|
|
||||||
context->phase++;break;
|
|
||||||
case 4:
|
case 4:
|
||||||
if(context->completion_count!=1)return G_SOURCE_CONTINUE;
|
if(context->completion_count!=1)return G_SOURCE_CONTINUE;
|
||||||
g_assert_true(context->persisted);
|
g_assert_true(context->persisted);
|
||||||
|
|
@ -577,7 +650,7 @@ static void activate(GtkApplication *application,gpointer data)
|
||||||
"document",png_size,png_sha,"2026-07-28T10:00:00Z",NULL,NULL,
|
"document",png_size,png_sha,"2026-07-28T10:00:00Z",NULL,NULL,
|
||||||
"SPECIMEN synthétique",EVIDENCE_INTEGRITY_STATUS_VALID,&error);
|
"SPECIMEN synthétique",EVIDENCE_INTEGRITY_STATUS_VALID,&error);
|
||||||
g_assert_nonnull(record);evidence_record_set_display_metadata(
|
g_assert_nonnull(record);evidence_record_set_display_metadata(
|
||||||
record,"Document d’identité","image/png");g_ptr_array_add(records,record);
|
record,"Document d’identité",NULL);g_ptr_array_add(records,record);
|
||||||
record=evidence_record_new(
|
record=evidence_record_new(
|
||||||
"10000000-0000-4000-8000-000000000002","SPECIMEN-page-2.pdf",
|
"10000000-0000-4000-8000-000000000002","SPECIMEN-page-2.pdf",
|
||||||
"SPECIMEN-page-2.pdf",
|
"SPECIMEN-page-2.pdf",
|
||||||
|
|
@ -611,6 +684,10 @@ static void activate(GtkApplication *application,gpointer data)
|
||||||
GList *windows=gtk_application_get_windows(application);
|
GList *windows=gtk_application_get_windows(application);
|
||||||
context->dialog=windows->data==context->main_window
|
context->dialog=windows->data==context->main_window
|
||||||
?GTK_WINDOW(windows->next->data):GTK_WINDOW(windows->data);
|
?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),
|
GtkWidget *designation=find_entry(GTK_WIDGET(context->dialog),
|
||||||
"Personne présumée liée aux comptes");
|
"Personne présumée liée aux comptes");
|
||||||
gtk_editable_set_text(GTK_EDITABLE(designation),"PERSONNE SPECIMEN");
|
gtk_editable_set_text(GTK_EDITABLE(designation),"PERSONNE SPECIMEN");
|
||||||
|
|
|
||||||
119
tests/test_dialog_geometry_gtk.c
Normal file
119
tests/test_dialog_geometry_gtk.c
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include "views/dialog_geometry.h"
|
||||||
|
|
||||||
|
static void test_calculations(void)
|
||||||
|
{
|
||||||
|
LabfyDialogGeometry geometry =
|
||||||
|
labfy_dialog_geometry_calculate(1920, 1080);
|
||||||
|
g_assert_cmpint(geometry.initial_width, ==, 1200);
|
||||||
|
g_assert_cmpint(geometry.initial_height, ==, 800);
|
||||||
|
g_assert_cmpint(geometry.minimum_width, ==, 800);
|
||||||
|
g_assert_cmpint(geometry.minimum_height, ==, 600);
|
||||||
|
geometry = labfy_dialog_geometry_calculate(800, 600);
|
||||||
|
g_assert_cmpint(geometry.initial_width, ==, 800);
|
||||||
|
g_assert_cmpint(geometry.initial_height, ==, 600);
|
||||||
|
g_assert_cmpint(geometry.minimum_width, ==, 800);
|
||||||
|
g_assert_cmpint(geometry.minimum_height, ==, 600);
|
||||||
|
geometry = labfy_dialog_geometry_calculate(760, 560);
|
||||||
|
g_assert_cmpint(geometry.initial_width, ==, 684);
|
||||||
|
g_assert_cmpint(geometry.initial_height, ==, 504);
|
||||||
|
g_assert_cmpint(geometry.minimum_width, ==, 684);
|
||||||
|
g_assert_cmpint(geometry.minimum_height, ==, 504);
|
||||||
|
geometry = labfy_dialog_geometry_calculate(0, 0);
|
||||||
|
g_assert_cmpint(geometry.initial_width, ==, 1200);
|
||||||
|
g_assert_cmpint(geometry.initial_height, ==, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean frame_seen(GtkWidget *widget, GdkFrameClock *clock,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gboolean *seen = data;
|
||||||
|
(void) widget;
|
||||||
|
(void) clock;
|
||||||
|
*seen = TRUE;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_frame(GtkWidget *widget)
|
||||||
|
{
|
||||||
|
gboolean seen = FALSE;
|
||||||
|
gtk_widget_add_tick_callback(widget, frame_seen, &seen, NULL);
|
||||||
|
while (!seen)
|
||||||
|
g_main_context_iteration(NULL, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_paned_applied_once(void)
|
||||||
|
{
|
||||||
|
GtkWindow *window = GTK_WINDOW(gtk_window_new());
|
||||||
|
GtkPaned *paned = GTK_PANED(
|
||||||
|
gtk_paned_new(GTK_ORIENTATION_HORIZONTAL));
|
||||||
|
gtk_paned_set_start_child(paned, gtk_label_new("Formulaire"));
|
||||||
|
gtk_paned_set_end_child(paned, gtk_label_new("Aperçu"));
|
||||||
|
gtk_window_set_default_size(window, 900, 600);
|
||||||
|
gtk_window_set_child(window, GTK_WIDGET(paned));
|
||||||
|
labfy_paned_apply_initial_ratio(paned, 2.0 / 3.0, 320, 180);
|
||||||
|
gtk_window_present(window);
|
||||||
|
for (guint frame = 0; frame < 6; frame++)
|
||||||
|
wait_for_frame(GTK_WIDGET(window));
|
||||||
|
int expected = (gtk_widget_get_width(GTK_WIDGET(paned)) * 2) / 3;
|
||||||
|
g_assert_cmpint(ABS(gtk_paned_get_position(paned) - expected), <=, 2);
|
||||||
|
gtk_paned_set_position(paned, 450);
|
||||||
|
gtk_window_set_default_size(window, 1000, 700);
|
||||||
|
wait_for_frame(GTK_WIDGET(window));
|
||||||
|
g_assert_cmpint(gtk_paned_get_position(paned), ==, 450);
|
||||||
|
labfy_paned_apply_initial_ratio(paned, 0.5, 100, 100);
|
||||||
|
g_assert_cmpint(gtk_paned_get_position(paned), ==, 450);
|
||||||
|
gtk_window_destroy(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_parent_and_presentation(void)
|
||||||
|
{
|
||||||
|
for (guint cycle = 0; cycle < 20; cycle++) {
|
||||||
|
GtkWindow *parent = GTK_WINDOW(gtk_window_new());
|
||||||
|
GtkWindow *dialog = GTK_WINDOW(gtk_window_new());
|
||||||
|
GtkWindow *nested = GTK_WINDOW(gtk_window_new());
|
||||||
|
GtkWindow *dialog_observer = dialog;
|
||||||
|
GtkWindow *nested_observer = nested;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
gtk_window_present(parent);
|
||||||
|
labfy_dialog_prepare(dialog, parent, TRUE, TRUE);
|
||||||
|
g_assert_true(gtk_window_get_transient_for(dialog) == parent);
|
||||||
|
g_assert_true(gtk_window_get_modal(dialog));
|
||||||
|
g_assert_true(gtk_window_get_destroy_with_parent(dialog));
|
||||||
|
labfy_dialog_present(dialog);
|
||||||
|
gtk_window_get_default_size(dialog, &width, &height);
|
||||||
|
g_assert_cmpint(width, >, 0);
|
||||||
|
g_assert_cmpint(height, >, 0);
|
||||||
|
g_assert_true(gtk_widget_get_visible(GTK_WIDGET(dialog)));
|
||||||
|
|
||||||
|
labfy_dialog_prepare(nested, dialog, TRUE, TRUE);
|
||||||
|
g_assert_true(gtk_window_get_transient_for(nested) == dialog);
|
||||||
|
labfy_dialog_present(nested);
|
||||||
|
g_assert_true(gtk_widget_get_visible(GTK_WIDGET(nested)));
|
||||||
|
|
||||||
|
g_object_add_weak_pointer(
|
||||||
|
G_OBJECT(dialog), (gpointer *) &dialog_observer);
|
||||||
|
g_object_add_weak_pointer(
|
||||||
|
G_OBJECT(nested), (gpointer *) &nested_observer);
|
||||||
|
gtk_window_destroy(parent);
|
||||||
|
while (g_main_context_iteration(NULL, FALSE));
|
||||||
|
g_assert_null(dialog_observer);
|
||||||
|
g_assert_null(nested_observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (!gtk_init_check()) {
|
||||||
|
g_print("SKIP: aucun affichage GTK disponible.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
g_test_init(&argc, &argv, NULL);
|
||||||
|
g_test_add_func("/dialog-geometry/calculations", test_calculations);
|
||||||
|
g_test_add_func("/dialog-geometry/paned-applied-once",
|
||||||
|
test_paned_applied_once);
|
||||||
|
g_test_add_func("/dialog-geometry/parent-and-presentation",
|
||||||
|
test_parent_and_presentation);
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
|
|
@ -452,6 +452,10 @@ static gboolean drive(gpointer data)
|
||||||
if (context->phase == 0) {
|
if (context->phase == 0) {
|
||||||
GtkWindow *dialog = find_window(context, "Importer une preuve");
|
GtkWindow *dialog = find_window(context, "Importer une preuve");
|
||||||
if (dialog == NULL) return G_SOURCE_CONTINUE;
|
if (dialog == NULL) return G_SOURCE_CONTINUE;
|
||||||
|
g_assert_true(gtk_window_get_transient_for(dialog) ==
|
||||||
|
context->main_window);
|
||||||
|
g_assert_true(gtk_window_get_modal(dialog));
|
||||||
|
g_assert_true(gtk_window_get_destroy_with_parent(dialog));
|
||||||
GtkWidget *paned =
|
GtkWidget *paned =
|
||||||
find_named(GTK_WIDGET(dialog), "evidence-import-paned");
|
find_named(GTK_WIDGET(dialog), "evidence-import-paned");
|
||||||
g_assert_true(GTK_IS_PANED(paned));
|
g_assert_true(GTK_IS_PANED(paned));
|
||||||
|
|
@ -465,11 +469,12 @@ static gboolean drive(gpointer data)
|
||||||
GtkWindow *dialog = find_window(
|
GtkWindow *dialog = find_window(
|
||||||
context, "Import — OCR d’identité facultatif");
|
context, "Import — OCR d’identité facultatif");
|
||||||
if (dialog == NULL) return G_SOURCE_CONTINUE;
|
if (dialog == NULL) return G_SOURCE_CONTINUE;
|
||||||
|
g_assert_nonnull(gtk_window_get_transient_for(dialog));
|
||||||
|
g_assert_true(gtk_window_get_modal(dialog));
|
||||||
|
g_assert_true(gtk_window_get_destroy_with_parent(dialog));
|
||||||
GtkWidget *root = GTK_WIDGET(dialog);
|
GtkWidget *root = GTK_WIDGET(dialog);
|
||||||
int default_width = 0;
|
int default_width = 0;
|
||||||
int default_height = 0;
|
int default_height = 0;
|
||||||
if (!context->layout_resize_pending)
|
|
||||||
gtk_window_set_default_size(dialog, 1200, 800);
|
|
||||||
gtk_window_get_default_size(
|
gtk_window_get_default_size(
|
||||||
dialog, &default_width, &default_height);
|
dialog, &default_width, &default_height);
|
||||||
g_assert_cmpint(default_width, ==,
|
g_assert_cmpint(default_width, ==,
|
||||||
|
|
@ -490,14 +495,16 @@ static gboolean drive(gpointer data)
|
||||||
g_assert_true(gtk_paned_get_start_child(GTK_PANED(paned)) == left);
|
g_assert_true(gtk_paned_get_start_child(GTK_PANED(paned)) == left);
|
||||||
g_assert_nonnull(gtk_paned_get_end_child(GTK_PANED(paned)));
|
g_assert_nonnull(gtk_paned_get_end_child(GTK_PANED(paned)));
|
||||||
g_assert_false(is_descendant_of(actions, form_scroll));
|
g_assert_false(is_descendant_of(actions, form_scroll));
|
||||||
if (g_object_get_data(
|
if (gtk_widget_get_width(paned) <= 0 ||
|
||||||
G_OBJECT(paned), "identity-initial-position-set") == NULL)
|
gtk_paned_get_position(GTK_PANED(paned)) <= 0)
|
||||||
return G_SOURCE_CONTINUE;
|
return G_SOURCE_CONTINUE;
|
||||||
if (!context->layout_resize_pending) {
|
if (!context->layout_resize_pending) {
|
||||||
int initial_position =
|
int initial_position =
|
||||||
gtk_paned_get_position(GTK_PANED(paned));
|
gtk_paned_get_position(GTK_PANED(paned));
|
||||||
g_assert_cmpint(initial_position, >=, 780);
|
int expected_position =
|
||||||
g_assert_cmpint(initial_position, <=, 820);
|
(gtk_widget_get_width(paned) * 2) / 3;
|
||||||
|
g_assert_cmpint(
|
||||||
|
ABS(initial_position - expected_position), <=, 3);
|
||||||
gtk_paned_set_position(GTK_PANED(paned), 650);
|
gtk_paned_set_position(GTK_PANED(paned), 650);
|
||||||
g_assert_cmpint(
|
g_assert_cmpint(
|
||||||
gtk_paned_get_position(GTK_PANED(paned)), ==, 650);
|
gtk_paned_get_position(GTK_PANED(paned)), ==, 650);
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,12 @@ static gboolean drive(gpointer data)
|
||||||
g_test_message("drive phase=%u fenêtre=%s cycles=%u",
|
g_test_message("drive phase=%u fenêtre=%s cycles=%u",
|
||||||
context->phase, dialog != NULL ? "oui" : "non",
|
context->phase, dialog != NULL ? "oui" : "non",
|
||||||
context->close_cycles);
|
context->close_cycles);
|
||||||
|
if (dialog != NULL) {
|
||||||
|
g_assert_true(gtk_window_get_transient_for(dialog) ==
|
||||||
|
context->main_window);
|
||||||
|
g_assert_true(gtk_window_get_modal(dialog));
|
||||||
|
g_assert_true(gtk_window_get_destroy_with_parent(dialog));
|
||||||
|
}
|
||||||
if (dialog == NULL) {
|
if (dialog == NULL) {
|
||||||
if (context->phase == 1) {
|
if (context->phase == 1) {
|
||||||
insert_run(context, FALSE);
|
insert_run(context, FALSE);
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,64 @@ static void test_preview_pdf_first_page(void)
|
||||||
g_assert_cmpint(g_rmdir(root), ==, 0); g_free(path); g_free(root);
|
g_assert_cmpint(g_rmdir(root), ==, 0); g_free(path); g_free(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_preview_pdf_multiple_pages_and_limits(void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
char *root = g_dir_make_tmp("labfy-preview-pdf-pages-XXXXXX", &error);
|
||||||
|
char *path = g_build_filename(root, "SPECIMEN-3-pages.pdf", NULL);
|
||||||
|
cairo_surface_t *surface = cairo_pdf_surface_create(path, 320, 200);
|
||||||
|
cairo_t *cr = cairo_create(surface);
|
||||||
|
for (guint page = 1; page <= 3; page++) {
|
||||||
|
char *label = g_strdup_printf("PAGE %u SPECIMEN", page);
|
||||||
|
cairo_set_source_rgb(cr, page / 3.0, 0.2, 0.4);
|
||||||
|
cairo_paint(cr);
|
||||||
|
cairo_set_source_rgb(cr, 1, 1, 1);
|
||||||
|
cairo_move_to(cr, 20, 40);
|
||||||
|
cairo_show_text(cr, label);
|
||||||
|
cairo_show_page(cr);
|
||||||
|
g_free(label);
|
||||||
|
}
|
||||||
|
cairo_destroy(cr);
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
|
char *sha256 = NULL;
|
||||||
|
guint64 size = 0;
|
||||||
|
g_assert_true(file_hash_compute_sha256(
|
||||||
|
path, NULL, &sha256, &size, &error));
|
||||||
|
g_assert_no_error(error);
|
||||||
|
EvidencePreviewRequest *request = evidence_preview_request_new(
|
||||||
|
root, "10000000-0000-4000-8000-000000000120",
|
||||||
|
"SPECIMEN-3-pages.pdf", sha256, "application/pdf", 20);
|
||||||
|
EvidencePreviewResult *first =
|
||||||
|
evidence_preview_load(request, NULL, &error);
|
||||||
|
g_assert_no_error(error);
|
||||||
|
g_assert_cmpuint(first->item_count, ==, 3);
|
||||||
|
g_assert_cmpuint(first->current_page, ==, 1);
|
||||||
|
evidence_preview_result_free(first);
|
||||||
|
evidence_preview_request_set_pdf_page(request, 1);
|
||||||
|
EvidencePreviewResult *second =
|
||||||
|
evidence_preview_load(request, NULL, &error);
|
||||||
|
g_assert_no_error(error);
|
||||||
|
g_assert_cmpuint(second->current_page, ==, 2);
|
||||||
|
evidence_preview_result_free(second);
|
||||||
|
evidence_preview_request_set_pdf_page(request, 2);
|
||||||
|
EvidencePreviewResult *last =
|
||||||
|
evidence_preview_load(request, NULL, &error);
|
||||||
|
g_assert_no_error(error);
|
||||||
|
g_assert_cmpuint(last->current_page, ==, 3);
|
||||||
|
evidence_preview_result_free(last);
|
||||||
|
evidence_preview_request_set_pdf_page(request, 3);
|
||||||
|
g_assert_null(evidence_preview_load(request, NULL, &error));
|
||||||
|
g_assert_error(error,
|
||||||
|
g_quark_from_static_string("evidence-preview-error"), 8);
|
||||||
|
g_clear_error(&error);
|
||||||
|
evidence_preview_request_free(request);
|
||||||
|
g_free(sha256);
|
||||||
|
g_assert_cmpint(g_remove(path), ==, 0);
|
||||||
|
g_assert_cmpint(g_rmdir(root), ==, 0);
|
||||||
|
g_free(path);
|
||||||
|
g_free(root);
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean write_heic_specimen(const char *path, gint width, gint height)
|
static gboolean write_heic_specimen(const char *path, gint width, gint height)
|
||||||
{
|
{
|
||||||
struct heif_context *context = heif_context_alloc();
|
struct heif_context *context = heif_context_alloc();
|
||||||
|
|
@ -379,6 +437,8 @@ int main(int argc, char **argv)
|
||||||
test_preview_dispatch_text_video_email);
|
test_preview_dispatch_text_video_email);
|
||||||
g_test_add_func("/evidence-preview/pdf-first-page",
|
g_test_add_func("/evidence-preview/pdf-first-page",
|
||||||
test_preview_pdf_first_page);
|
test_preview_pdf_first_page);
|
||||||
|
g_test_add_func("/evidence-preview/pdf-multiple-pages-limits",
|
||||||
|
test_preview_pdf_multiple_pages_and_limits);
|
||||||
g_test_add_func("/evidence-preview/real-heic",
|
g_test_add_func("/evidence-preview/real-heic",
|
||||||
test_preview_real_heic);
|
test_preview_real_heic);
|
||||||
g_test_add_func("/evidence-preview/heic-cancel-invalid",
|
g_test_add_func("/evidence-preview/heic-cancel-invalid",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,58 @@ static void drain_until(EvidencePreviewWidget *widget, const char *state)
|
||||||
g_main_context_iteration(NULL, TRUE);
|
g_main_context_iteration(NULL, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean mark_frame(GtkWidget *widget, GdkFrameClock *clock,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
gboolean *frame_seen = data;
|
||||||
|
(void) widget;
|
||||||
|
(void) clock;
|
||||||
|
*frame_seen = TRUE;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_frame(GtkWidget *widget)
|
||||||
|
{
|
||||||
|
gboolean frame_seen = FALSE;
|
||||||
|
gtk_widget_add_tick_callback(widget, mark_frame, &frame_seen, NULL);
|
||||||
|
while (!frame_seen)
|
||||||
|
g_main_context_iteration(NULL, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean task_manager_has_running_task(TaskManager *manager)
|
||||||
|
{
|
||||||
|
gsize count = task_manager_get_count(manager);
|
||||||
|
for (gsize index = 0; index < count; index++) {
|
||||||
|
BackgroundTask *task = task_manager_get_task(manager, index);
|
||||||
|
BackgroundTaskState state = background_task_get_state(task);
|
||||||
|
background_task_unref(task);
|
||||||
|
if (state == BACKGROUND_TASK_STATE_PENDING ||
|
||||||
|
state == BACKGROUND_TASK_STATE_RUNNING)
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *found = find_named(child, name);
|
||||||
|
if (found != NULL) return found;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void click_named(EvidencePreviewWidget *widget, const char *name)
|
||||||
|
{
|
||||||
|
GtkWidget *button = find_named(
|
||||||
|
evidence_preview_widget_get_widget(widget), name);
|
||||||
|
g_assert_nonnull(button);
|
||||||
|
g_assert_true(gtk_widget_get_sensitive(button));
|
||||||
|
g_signal_emit_by_name(button, "clicked");
|
||||||
|
}
|
||||||
|
|
||||||
static void test_widget_lifecycle_and_eml(void)
|
static void test_widget_lifecycle_and_eml(void)
|
||||||
{
|
{
|
||||||
static const char eml[] =
|
static const char eml[] =
|
||||||
|
|
@ -89,12 +141,17 @@ static void write_pdf(const char *path)
|
||||||
{
|
{
|
||||||
cairo_surface_t *surface = cairo_pdf_surface_create(path, 640, 360);
|
cairo_surface_t *surface = cairo_pdf_surface_create(path, 640, 360);
|
||||||
cairo_t *cr = cairo_create(surface);
|
cairo_t *cr = cairo_create(surface);
|
||||||
cairo_set_source_rgb(cr, 1, 1, 1);
|
for (guint page = 1; page <= 3; page++) {
|
||||||
cairo_paint(cr);
|
char *label = g_strdup_printf("PAGE PDF %u SPECIMEN", page);
|
||||||
cairo_set_source_rgb(cr, 0, 0, 0);
|
cairo_set_source_rgb(cr, page == 1 ? 0.8 : 0.2,
|
||||||
cairo_move_to(cr, 30, 80);
|
page == 2 ? 0.8 : 0.2, page == 3 ? 0.8 : 0.2);
|
||||||
cairo_show_text(cr, "PAGE PDF SPECIMEN");
|
cairo_paint(cr);
|
||||||
cairo_show_page(cr);
|
cairo_set_source_rgb(cr, 0, 0, 0);
|
||||||
|
cairo_move_to(cr, 30, 80);
|
||||||
|
cairo_show_text(cr, label);
|
||||||
|
cairo_show_page(cr);
|
||||||
|
g_free(label);
|
||||||
|
}
|
||||||
cairo_destroy(cr);
|
cairo_destroy(cr);
|
||||||
cairo_surface_destroy(surface);
|
cairo_surface_destroy(surface);
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +218,34 @@ static void test_paned_png_jpeg_pdf(void)
|
||||||
evidence_preview_widget_show(widget, png_request, "PNG SPECIMEN");
|
evidence_preview_widget_show(widget, png_request, "PNG SPECIMEN");
|
||||||
drain_until(widget, "image");
|
drain_until(widget, "image");
|
||||||
g_assert_cmpstr(evidence_preview_widget_get_state(widget), ==, "image");
|
g_assert_cmpstr(evidence_preview_widget_get_state(widget), ==, "image");
|
||||||
|
g_assert_true(evidence_preview_widget_is_fit(widget));
|
||||||
|
click_named(widget, "evidence-preview-zoom-in");
|
||||||
|
g_assert_cmpfloat(evidence_preview_widget_get_zoom(widget), ==, 1.25);
|
||||||
|
for (guint index = 0; index < 4; index++)
|
||||||
|
click_named(widget, "evidence-preview-zoom-in");
|
||||||
|
g_assert_cmpfloat(evidence_preview_widget_get_zoom(widget), ==, 4.0);
|
||||||
|
wait_for_frame(GTK_WIDGET(window));
|
||||||
|
GtkScrolledWindow *image_scroll = GTK_SCROLLED_WINDOW(find_named(
|
||||||
|
evidence_preview_widget_get_widget(widget),
|
||||||
|
"evidence-preview-image-scroll"));
|
||||||
|
GtkAdjustment *horizontal =
|
||||||
|
gtk_scrolled_window_get_hadjustment(image_scroll);
|
||||||
|
GtkAdjustment *vertical =
|
||||||
|
gtk_scrolled_window_get_vadjustment(image_scroll);
|
||||||
|
g_assert_cmpfloat(gtk_adjustment_get_upper(horizontal), >,
|
||||||
|
gtk_adjustment_get_page_size(horizontal));
|
||||||
|
g_assert_cmpfloat(gtk_adjustment_get_upper(vertical), >,
|
||||||
|
gtk_adjustment_get_page_size(vertical));
|
||||||
|
gtk_adjustment_set_value(horizontal,
|
||||||
|
gtk_adjustment_get_upper(horizontal) / 3.0);
|
||||||
|
gtk_adjustment_set_value(vertical,
|
||||||
|
gtk_adjustment_get_upper(vertical) / 3.0);
|
||||||
|
g_assert_cmpfloat(gtk_adjustment_get_value(horizontal), >, 0.0);
|
||||||
|
g_assert_cmpfloat(gtk_adjustment_get_value(vertical), >, 0.0);
|
||||||
|
click_named(widget, "evidence-preview-zoom-out");
|
||||||
|
g_assert_cmpfloat(evidence_preview_widget_get_zoom(widget), ==, 3.0);
|
||||||
|
click_named(widget, "evidence-preview-fit");
|
||||||
|
g_assert_true(evidence_preview_widget_is_fit(widget));
|
||||||
EvidencePreviewRequest *jpeg_request = request_for(
|
EvidencePreviewRequest *jpeg_request = request_for(
|
||||||
directory, "SPECIMEN.jpg",
|
directory, "SPECIMEN.jpg",
|
||||||
"10000000-0000-4000-8000-000000000002", "image/jpeg", 2);
|
"10000000-0000-4000-8000-000000000002", "image/jpeg", 2);
|
||||||
|
|
@ -173,10 +258,48 @@ static void test_paned_png_jpeg_pdf(void)
|
||||||
evidence_preview_widget_show(widget, pdf_request, "PDF SPECIMEN");
|
evidence_preview_widget_show(widget, pdf_request, "PDF SPECIMEN");
|
||||||
drain_until(widget, "pdf");
|
drain_until(widget, "pdf");
|
||||||
g_assert_cmpstr(evidence_preview_widget_get_state(widget), ==, "pdf");
|
g_assert_cmpstr(evidence_preview_widget_get_state(widget), ==, "pdf");
|
||||||
|
g_assert_cmpuint(evidence_preview_widget_get_pdf_page_count(widget), ==, 3);
|
||||||
|
g_assert_cmpuint(evidence_preview_widget_get_pdf_page(widget), ==, 0);
|
||||||
|
GtkWidget *previous = find_named(
|
||||||
|
evidence_preview_widget_get_widget(widget),
|
||||||
|
"evidence-preview-previous-page");
|
||||||
|
GtkWidget *next = find_named(
|
||||||
|
evidence_preview_widget_get_widget(widget),
|
||||||
|
"evidence-preview-next-page");
|
||||||
|
GtkLabel *page_label = GTK_LABEL(find_named(
|
||||||
|
evidence_preview_widget_get_widget(widget),
|
||||||
|
"evidence-preview-page-label"));
|
||||||
|
g_assert_false(gtk_widget_get_sensitive(previous));
|
||||||
|
g_assert_true(gtk_widget_get_sensitive(next));
|
||||||
|
g_assert_cmpstr(gtk_label_get_text(page_label), ==, "Page 1 / 3");
|
||||||
|
click_named(widget, "evidence-preview-next-page");
|
||||||
|
drain_until(widget, "pdf");
|
||||||
|
g_assert_cmpuint(evidence_preview_widget_get_pdf_page(widget), ==, 1);
|
||||||
|
g_assert_cmpstr(gtk_label_get_text(page_label), ==, "Page 2 / 3");
|
||||||
|
click_named(widget, "evidence-preview-zoom-in");
|
||||||
|
g_assert_cmpfloat(evidence_preview_widget_get_zoom(widget), ==, 1.25);
|
||||||
|
click_named(widget, "evidence-preview-next-page");
|
||||||
|
drain_until(widget, "pdf");
|
||||||
|
g_assert_cmpuint(evidence_preview_widget_get_pdf_page(widget), ==, 2);
|
||||||
|
g_assert_false(gtk_widget_get_sensitive(next));
|
||||||
|
g_assert_cmpfloat(evidence_preview_widget_get_zoom(widget), ==, 1.25);
|
||||||
|
click_named(widget, "evidence-preview-previous-page");
|
||||||
|
click_named(widget, "evidence-preview-previous-page");
|
||||||
|
drain_until(widget, "pdf");
|
||||||
|
g_assert_cmpuint(evidence_preview_widget_get_pdf_page(widget), ==, 0);
|
||||||
|
g_assert_false(gtk_widget_get_sensitive(previous));
|
||||||
|
click_named(widget, "evidence-preview-next-page");
|
||||||
|
click_named(widget, "evidence-preview-next-page");
|
||||||
|
click_named(widget, "evidence-preview-previous-page");
|
||||||
|
drain_until(widget, "pdf");
|
||||||
|
g_assert_cmpuint(evidence_preview_widget_get_pdf_page(widget), ==, 1);
|
||||||
gtk_window_set_default_size(window, 760, 560);
|
gtk_window_set_default_size(window, 760, 560);
|
||||||
gtk_paned_set_position(GTK_PANED(paned), 300);
|
gtk_paned_set_position(GTK_PANED(paned), 300);
|
||||||
while (g_main_context_iteration(NULL, FALSE));
|
while (g_main_context_iteration(NULL, FALSE));
|
||||||
g_assert_cmpint(gtk_paned_get_position(GTK_PANED(paned)), ==, 300);
|
g_assert_cmpint(gtk_paned_get_position(GTK_PANED(paned)), ==, 300);
|
||||||
|
g_assert_true(gtk_widget_get_mapped(find_named(
|
||||||
|
evidence_preview_widget_get_widget(widget),
|
||||||
|
"evidence-preview-toolbar")));
|
||||||
evidence_preview_widget_show(widget, png_request, "résultat ancien");
|
evidence_preview_widget_show(widget, png_request, "résultat ancien");
|
||||||
evidence_preview_widget_show(widget, jpeg_request, "résultat courant");
|
evidence_preview_widget_show(widget, jpeg_request, "résultat courant");
|
||||||
drain_until(widget, "image");
|
drain_until(widget, "image");
|
||||||
|
|
@ -199,6 +322,46 @@ static void test_paned_png_jpeg_pdf(void)
|
||||||
g_free(directory);
|
g_free(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_pdf_twenty_open_close_cycles(void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
char *directory =
|
||||||
|
g_dir_make_tmp("labfy-preview-cycles-XXXXXX", &error);
|
||||||
|
g_assert_no_error(error);
|
||||||
|
char *pdf = g_build_filename(directory, "SPECIMEN-multipage.pdf", NULL);
|
||||||
|
write_pdf(pdf);
|
||||||
|
TaskManager *manager = task_manager_new();
|
||||||
|
EvidencePreviewRequest *request = request_for(
|
||||||
|
directory, "SPECIMEN-multipage.pdf",
|
||||||
|
"10000000-0000-4000-8000-000000000020", "application/pdf", 20);
|
||||||
|
for (guint cycle = 0; cycle < 20; cycle++) {
|
||||||
|
EvidencePreviewWidget *widget =
|
||||||
|
evidence_preview_widget_new(manager, NULL, NULL);
|
||||||
|
GtkWindow *window = GTK_WINDOW(gtk_window_new());
|
||||||
|
gtk_window_set_default_size(window, 760, 560);
|
||||||
|
gtk_window_set_child(window,
|
||||||
|
evidence_preview_widget_get_widget(widget));
|
||||||
|
gtk_window_present(window);
|
||||||
|
evidence_preview_widget_show(widget, request, "PDF SPECIMEN");
|
||||||
|
if ((cycle % 2U) == 0) {
|
||||||
|
drain_until(widget, "pdf");
|
||||||
|
click_named(widget, "evidence-preview-next-page");
|
||||||
|
}
|
||||||
|
gtk_window_destroy(window);
|
||||||
|
evidence_preview_widget_cancel(widget);
|
||||||
|
evidence_preview_widget_free(widget);
|
||||||
|
}
|
||||||
|
while (task_manager_has_running_task(manager))
|
||||||
|
g_main_context_iteration(NULL, TRUE);
|
||||||
|
while (g_main_context_iteration(NULL, FALSE));
|
||||||
|
task_manager_free(manager);
|
||||||
|
evidence_preview_request_free(request);
|
||||||
|
g_unlink(pdf);
|
||||||
|
g_rmdir(directory);
|
||||||
|
g_free(pdf);
|
||||||
|
g_free(directory);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (!gtk_init_check()) {
|
if (!gtk_init_check()) {
|
||||||
|
|
@ -210,5 +373,7 @@ int main(int argc, char **argv)
|
||||||
test_widget_lifecycle_and_eml);
|
test_widget_lifecycle_and_eml);
|
||||||
g_test_add_func("/evidence-preview-widget/paned-png-jpeg-pdf",
|
g_test_add_func("/evidence-preview-widget/paned-png-jpeg-pdf",
|
||||||
test_paned_png_jpeg_pdf);
|
test_paned_png_jpeg_pdf);
|
||||||
|
g_test_add_func("/evidence-preview-widget/pdf-twenty-open-close-cycles",
|
||||||
|
test_pdf_twenty_open_close_cycles);
|
||||||
return g_test_run();
|
return g_test_run();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "widgets/ocr_provenance_overlay.h"
|
#include "widgets/ocr_provenance_overlay.h"
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
|
@ -10,22 +11,45 @@ int main(int argc, char **argv)
|
||||||
OcrProvenanceOverlay *overlay = ocr_provenance_overlay_new();
|
OcrProvenanceOverlay *overlay = ocr_provenance_overlay_new();
|
||||||
GtkWidget *widget = ocr_provenance_overlay_get_widget(overlay);
|
GtkWidget *widget = ocr_provenance_overlay_get_widget(overlay);
|
||||||
g_object_ref_sink(widget);
|
g_object_ref_sink(widget);
|
||||||
|
cairo_surface_t *surface = cairo_image_surface_create(
|
||||||
|
CAIRO_FORMAT_ARGB32, 800, 500);
|
||||||
|
char *path = g_strdup_printf("%s/labfy-provenance-%u.png",
|
||||||
|
g_get_tmp_dir(), g_random_int());
|
||||||
|
g_assert_cmpint(cairo_surface_write_to_png(surface, path),
|
||||||
|
==, CAIRO_STATUS_SUCCESS);
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
|
char *contents = NULL;
|
||||||
|
gsize length = 0;
|
||||||
|
g_assert_true(g_file_get_contents(path, &contents, &length, NULL));
|
||||||
|
GBytes *bytes = g_bytes_new_take(contents, length);
|
||||||
|
ocr_provenance_overlay_set_image(overlay, bytes, 1);
|
||||||
|
ocr_provenance_overlay_set_page(overlay, 2, 2);
|
||||||
IdentitySourceBox box = {.page=2,.x=10,.y=20,.width=80,.height=25,
|
IdentitySourceBox box = {.page=2,.x=10,.y=20,.width=80,.height=25,
|
||||||
.image_width=800,.image_height=500,.available=TRUE};
|
.image_width=800,.image_height=500,.available=TRUE};
|
||||||
IdentityFieldObservation *field = identity_field_observation_new(
|
IdentityFieldObservation *field = identity_field_observation_new(
|
||||||
"surname", "SPECIMEN", 88, &box, 0);
|
"surname", "SPECIMEN", 88, &box, 0);
|
||||||
ocr_provenance_overlay_set_field(overlay, field, 2);
|
ocr_provenance_overlay_set_field(overlay, field, 2);
|
||||||
g_assert_true(ocr_provenance_overlay_has_region(overlay));
|
g_assert_true(ocr_provenance_overlay_has_region(overlay));
|
||||||
|
ocr_provenance_overlay_zoom_in(overlay);
|
||||||
|
g_assert_cmpfloat(ocr_provenance_overlay_get_zoom(overlay), ==, 1.25);
|
||||||
|
ocr_provenance_overlay_zoom_out(overlay);
|
||||||
|
ocr_provenance_overlay_fit(overlay);
|
||||||
|
g_assert_cmpfloat(ocr_provenance_overlay_get_zoom(overlay), ==, 0.0);
|
||||||
|
ocr_provenance_overlay_set_page(overlay, 3, 3);
|
||||||
|
g_assert_false(ocr_provenance_overlay_has_region(overlay));
|
||||||
IdentityFieldObservation *without = identity_field_observation_new(
|
IdentityFieldObservation *without = identity_field_observation_new(
|
||||||
"given_names", "ALICE", 70, NULL, 1);
|
"given_names", "ALICE", 70, NULL, 1);
|
||||||
ocr_provenance_overlay_set_field(overlay, without, 3);
|
ocr_provenance_overlay_set_field(overlay, without, 4);
|
||||||
g_assert_false(ocr_provenance_overlay_has_region(overlay));
|
g_assert_false(ocr_provenance_overlay_has_region(overlay));
|
||||||
ocr_provenance_overlay_set_field(overlay, field, 2);
|
ocr_provenance_overlay_set_field(overlay, field, 2);
|
||||||
g_assert_false(ocr_provenance_overlay_has_region(overlay));
|
g_assert_false(ocr_provenance_overlay_has_region(overlay));
|
||||||
ocr_provenance_overlay_clear(overlay, 4);
|
ocr_provenance_overlay_clear(overlay, 5);
|
||||||
identity_field_observation_free(field);
|
identity_field_observation_free(field);
|
||||||
identity_field_observation_free(without);
|
identity_field_observation_free(without);
|
||||||
ocr_provenance_overlay_free(overlay);
|
ocr_provenance_overlay_free(overlay);
|
||||||
g_object_unref(widget);
|
g_object_unref(widget);
|
||||||
|
g_bytes_unref(bytes);
|
||||||
|
g_remove(path);
|
||||||
|
g_free(path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue