generated from LouisMazin/PythonApplicationTemplate
splash screen and alerts
This commit is contained in:
parent
7531a1cbfa
commit
2e3be1f3ad
332
README.md
332
README.md
@ -1,162 +1,274 @@
|
|||||||
# 🚀 Python PyQt6 Application Template
|
# 🏥 HoDA Radio - Anonymisation d'Images Médicales
|
||||||
|
|
||||||
Bienvenue ! Ce projet est **bien plus qu'un simple template** : c'est une boîte à outils moderne pour créer rapidement des applications desktop Python élégantes, robustes et évolutives.
|
**HoDA Radio** est une application moderne et intuitive pour l'anonymisation d'images médicales DICOM. Développée en Python avec PyQt6, elle offre une interface élégante et des outils professionnels pour protéger la confidentialité des patients tout en conservant l'utilité médicale des images.
|
||||||
Vous voulez coder, personnaliser, traduire, mettre à jour, distribuer ? Tout est déjà prêt !
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Fonctionnalités clés
|
## ✨ Fonctionnalités principales
|
||||||
|
|
||||||
- **Interface moderne (PyQt6)** : Responsive, stylée, et facile à personnaliser.
|
### 🔒 Anonymisation avancée
|
||||||
- **Thèmes dynamiques** : Passez du clair au sombre en un clic, ou créez le vôtre !
|
- **Importation DICOM** : Support complet des fichiers et dossiers DICOM
|
||||||
- **Multi-langues** : Français, anglais... et ajoutez-en autant que vous voulez.
|
- **Édition en temps réel** : Outils de gomme et masquage intuitifs
|
||||||
- **Paramètres utilisateurs** : Tout est sauvegardé (thème, langue, taille de fenêtre...).
|
- **Prévisualisation instantanée** : Visualisation des modifications avant export
|
||||||
- **Architecture modulaire** : Des managers pour chaque besoin, tout est organisé.
|
- **Métadonnées** : Nettoyage automatique des informations sensibles
|
||||||
- **Notifications automatiques** : Les widgets s'adaptent instantanément aux changements.
|
|
||||||
- **Barre d’onglets flexible** : Ajoutez vos fenêtres où vous voulez, comme vous voulez.
|
### 🎨 Interface moderne
|
||||||
- **Système de suggestion** : Vos utilisateurs peuvent vous écrire directement depuis l’app.
|
- **Thèmes dynamiques** : Clair/sombre avec personnalisation complète
|
||||||
- **Mise à jour automatique** : Téléchargez la dernière version sans effort, avec barre de progression !
|
- **Multi-langues** : Français, anglais, espagnol (extensible)
|
||||||
- **Build & environnement** : Scripts pour tout automatiser, sur Windows, Linux, macOS.
|
- **Navigation fluide** : Barre d'onglets verticale avec icônes SVG
|
||||||
|
- **Responsive** : Interface adaptative selon la taille d'écran
|
||||||
|
|
||||||
|
### 📤 Export flexible
|
||||||
|
- **Formats multiples** : PNG, PDF, DCM, DICOMDIR
|
||||||
|
- **Métadonnées** : Export JSON et XLS des informations
|
||||||
|
- **Batch processing** : Traitement par lots pour l'efficacité
|
||||||
|
- **Préservation qualité** : Compression optimisée
|
||||||
|
|
||||||
|
### 🔄 Système intelligent
|
||||||
|
- **Mise à jour automatique** : Vérification et téléchargement des nouvelles versions
|
||||||
|
- **Splash screen utile** : Chargement avec feedback en temps réel
|
||||||
|
- **Gestion d'erreurs** : Messages informatifs et récupération gracieuse
|
||||||
|
- **Cache optimisé** : Performance améliorée pour les gros volumes
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🗂️ Structure du projet
|
## 🗂️ Architecture du projet
|
||||||
|
|
||||||
```
|
```
|
||||||
Template/
|
HoDA_Radio/
|
||||||
├── app/
|
├── app/
|
||||||
│ ├── core/ # Managers (thème, langue, update, etc.)
|
│ ├── core/ # Gestionnaires principaux
|
||||||
|
│ │ ├── main_manager.py # Chef d'orchestre
|
||||||
|
│ │ ├── dicom_manager.py # Gestion des fichiers DICOM
|
||||||
|
│ │ ├── theme_manager.py # Thèmes et styles
|
||||||
|
│ │ ├── language_manager.py # Traductions
|
||||||
|
│ │ ├── settings_manager.py # Paramètres utilisateur
|
||||||
|
│ │ ├── update_manager.py # Mises à jour
|
||||||
|
│ │ └── alert_manager.py # Messages et alertes
|
||||||
│ ├── ui/
|
│ ├── ui/
|
||||||
│ │ ├── widgets/ # Onglets, loading bar, etc.
|
│ │ ├── widgets/ # Composants réutilisables
|
||||||
│ │ └── windows/ # Paramètres, suggestion...
|
│ │ │ ├── tabs_widget.py # Barre d'onglets personnalisée
|
||||||
│ └── utils/ # Fonctions utilitaires
|
│ │ │ ├── loading_spinner.py # Indicateur de chargement
|
||||||
|
│ │ │ ├── loading_bar.py # Barre de progression
|
||||||
|
│ │ │ └── image_viewer.py # Visualiseur d'images DICOM
|
||||||
|
│ │ └── windows/ # Fenêtres principales
|
||||||
|
│ │ ├── main_window.py # Fenêtre principale
|
||||||
|
│ │ ├── splash_screen.py # Écran de chargement
|
||||||
|
│ │ ├── dicom_window.py # Interface d'édition DICOM
|
||||||
|
│ │ ├── import_window.py # Interface d'importation
|
||||||
|
│ │ ├── export_window.py # Interface d'exportation
|
||||||
|
│ │ ├── settings_window.py # Paramètres
|
||||||
|
│ │ └── suggestion_window.py # Feedback utilisateur
|
||||||
|
│ └── utils/ # Utilitaires
|
||||||
|
│ └── paths.py # Gestion des chemins
|
||||||
├── data/
|
├── data/
|
||||||
│ ├── assets/ # Icônes, images
|
│ ├── assets/ # Ressources (icônes, images)
|
||||||
│ ├── lang/ # Traductions
|
│ ├── lang/ # Fichiers de traduction
|
||||||
│ ├── themes/ # Thèmes
|
│ │ ├── fr.json # Français
|
||||||
│ └── others/ # Autres
|
│ │ ├── en.json # Anglais
|
||||||
├── tools/ # Scripts build/dev
|
│ │ └── es.json # Espagnol
|
||||||
├── config.json # Config principale
|
│ ├── themes/ # Thèmes visuels
|
||||||
├── requirements.txt # Dépendances
|
│ │ ├── dark.json # Thème sombre
|
||||||
├── BUILD.spec # PyInstaller
|
│ │ └── light.json # Thème clair
|
||||||
└── main.py # Point d’entrée
|
│ └── others/ # Configurations
|
||||||
|
│ └── defaults_settings.json
|
||||||
|
├── tools/ # Scripts de développement
|
||||||
|
│ ├── build.bat # Build Windows
|
||||||
|
│ ├── build.command # Build macOS
|
||||||
|
│ └── open.bat # Environnement de dev
|
||||||
|
├── config.json # Configuration principale
|
||||||
|
├── requirements.txt # Dépendances Python
|
||||||
|
├── BUILD.spec # Configuration PyInstaller
|
||||||
|
└── main.py # Point d'entrée
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚡ Démarrage Express
|
## ⚡ Installation et utilisation
|
||||||
|
|
||||||
1. **Configurez `config.json`**
|
### Pré-requis
|
||||||
(nom, version, OS, architecture, icône, dépôt git...)
|
- **Python 3.10+** (recommandé 3.11)
|
||||||
|
- **Système** : Windows 10+, macOS 10.15+, ou Linux Ubuntu 20.04+
|
||||||
|
|
||||||
2. **Copiez `.env.example` → `.env` et configurez les variables requises**
|
### 🚀 Démarrage rapide
|
||||||
- Depuis la racine du projet :
|
|
||||||
- Windows (PowerShell / cmd) : copy .env.example .env
|
|
||||||
- Linux / macOS : cp .env.example .env
|
|
||||||
- Au minimum, renseignez dans `.env` :
|
|
||||||
- PYTHON_PATH : chemin absolu vers votre exécutable Python (utilisé par tools/open.bat)
|
|
||||||
- les identifiants email si vous comptez utiliser l'envoi de suggestions (email + mot de passe / mot de passe d'application)
|
|
||||||
- Remarque : l'outil `tools/open.bat` s'appuie sur PYTHON_PATH ; sans cette variable, l'ouverture/initialisation de l'environnement échouera.
|
|
||||||
|
|
||||||
3. **Lancez le dev**
|
1. **Clonez le projet**
|
||||||
- Windows : tools\open.bat (nécessite PYTHON_PATH dans `.env`)
|
```bash
|
||||||
- Linux : tools/open.sh (si présent / exécutable)
|
git clone https://gitea.louismazin.ovh/LouisMazin/HoDA_Radio.git
|
||||||
- macOS : tools/open.command (si présent / exécutable)
|
cd HoDA_Radio
|
||||||
- Exécutez depuis leur fichier parent pour que les chemins relatifs fonctionnent correctement.
|
```
|
||||||
|
|
||||||
4. **Build en un clic**
|
2. **Configurez l'environnement**
|
||||||
- Windows : tools\build.bat
|
```bash
|
||||||
- Linux : tools/build.sh
|
# Copiez le fichier d'environnement
|
||||||
- macOS : tools/build.command
|
cp .env.example .env
|
||||||
- Ces scripts supposent que `.env` est configuré et que les outils requis (pyinstaller, etc.) sont installés.
|
|
||||||
|
# Éditez .env avec vos paramètres
|
||||||
|
# PYTHON_PATH=/chemin/vers/python
|
||||||
|
# EMAIL_USER=votre.email@gmail.com (optionnel)
|
||||||
|
# EMAIL_PASSWORD=votre_mot_de_passe (optionnel)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Lancez l'application**
|
||||||
|
- **Windows** : Double-cliquez sur `tools\open.bat`
|
||||||
|
- **macOS** : Exécutez `tools/open.command`
|
||||||
|
- **Linux** : Exécutez `tools/open.sh`
|
||||||
|
|
||||||
|
### 📦 Build de production
|
||||||
|
|
||||||
|
1. **Build automatique**
|
||||||
|
- **Windows** : `tools\build.bat`
|
||||||
|
- **macOS** : `tools/build.command`
|
||||||
|
- **Linux** : `tools/build.sh`
|
||||||
|
|
||||||
|
2. **Fichier généré**
|
||||||
|
- L'exécutable se trouve dans le dossier `build/`
|
||||||
|
- Nom automatique : `HoDA_Radio-{OS}-{ARCH}-v{VERSION}`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎨 Thèmes & 🌍 Langues
|
## 🎯 Utilisation de l'application
|
||||||
|
|
||||||
- **Thèmes** : Ajoutez vos fichiers dans `data/themes/` (JSON).
|
### 1. **Import des images DICOM**
|
||||||
Changez les couleurs, créez votre ambiance !
|
- Glissez-déposez vos fichiers ou dossiers DICOM
|
||||||
- **Langues** : Ajoutez vos fichiers dans `data/lang/` (JSON).
|
- Support des archives et structures complexes
|
||||||
Traduisez tout, c’est instantané.
|
- Validation automatique des formats
|
||||||
|
|
||||||
|
### 2. **Anonymisation interactive**
|
||||||
|
- Utilisez l'outil gomme pour masquer les zones sensibles
|
||||||
|
- Prévisualisez en temps réel vos modifications
|
||||||
|
- Ajustez la taille du pinceau selon vos besoins
|
||||||
|
|
||||||
|
### 3. **Export sécurisé**
|
||||||
|
- Choisissez vos formats de sortie
|
||||||
|
- Sélectionnez les images à exporter
|
||||||
|
- Nettoyage automatique des métadonnées
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧩 Managers & Architecture
|
## 🎨 Personnalisation
|
||||||
|
|
||||||
- **MainManager** : Le chef d’orchestre.
|
### Thèmes personnalisés
|
||||||
- **SettingsManager** : Les préférences utilisateur.
|
Créez votre thème dans `data/themes/mon_theme.json` :
|
||||||
- **ThemeManager** : Les couleurs et le style.
|
```json
|
||||||
- **LanguageManager** : Les textes traduits.
|
{
|
||||||
- **AlertManager** : Les messages, confirmations, erreurs.
|
"theme_name": "Mon Thème",
|
||||||
- **UpdateManager** : Les mises à jour automatiques.
|
"colors": {
|
||||||
- **ObserverManager** : Les notifications internes.
|
"background_color": "#1a1a1a",
|
||||||
|
"primary_color": "#007acc",
|
||||||
|
"text_color": "#ffffff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nouvelles langues
|
||||||
|
Ajoutez une traduction dans `data/lang/code_langue.json` :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lang_name": "Deutsch",
|
||||||
|
"language": "Sprache :",
|
||||||
|
"theme": "Thema :",
|
||||||
|
"loading": "Laden..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Développement avancé
|
||||||
|
|
||||||
|
### Architecture des managers
|
||||||
|
- **MainManager** : Singleton coordonnant tous les autres managers
|
||||||
|
- **DicomManager** : Logique métier pour les fichiers médicaux
|
||||||
|
- **ThemeManager** : Système de thèmes dynamiques avec cache
|
||||||
|
- **ObserverManager** : Pattern Observer pour les notifications inter-composants
|
||||||
|
|
||||||
|
### Ajout de fonctionnalités
|
||||||
|
1. Créez votre fenêtre dans `app/ui/windows/`
|
||||||
|
2. Ajoutez-la aux onglets dans `main_window.py`
|
||||||
|
3. Implémentez les notifications de thème/langue si nécessaire
|
||||||
|
|
||||||
|
### Tests et debugging
|
||||||
|
```bash
|
||||||
|
# Mode développement avec logs
|
||||||
|
python main.py --debug
|
||||||
|
|
||||||
|
# Tests unitaires (si implémentés)
|
||||||
|
python -m pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Dépendances principales
|
||||||
|
|
||||||
|
- **PyQt6** : Interface graphique moderne
|
||||||
|
- **pydicom** : Manipulation des fichiers DICOM
|
||||||
|
- **pillow** : Traitement d'images
|
||||||
|
- **numpy** : Calculs numériques
|
||||||
|
- **requests** : Mises à jour automatiques
|
||||||
|
- **packaging** : Gestion des versions
|
||||||
|
- **python-dotenv** : Variables d'environnement
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Sécurité et confidentialité
|
||||||
|
|
||||||
|
### Protection des données
|
||||||
|
- **Traitement local** : Aucune donnée médicale envoyée sur internet
|
||||||
|
- **Cache temporaire** : Nettoyage automatique à la fermeture
|
||||||
|
- **Métadonnées** : Suppression des informations personnelles
|
||||||
|
|
||||||
|
### Bonnes pratiques
|
||||||
|
- Utilisez des mots de passe d'application pour Gmail
|
||||||
|
- Ne versionnez jamais le fichier `.env`
|
||||||
|
- Vérifiez les exports avant diffusion
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔄 Mise à jour automatique
|
## 🔄 Mise à jour automatique
|
||||||
|
|
||||||
- Vérifie la dernière version sur le dépôt Git à chaque démarrage.
|
L'application vérifie automatiquement les nouvelles versions :
|
||||||
- Propose la mise à jour si disponible.
|
- **Au démarrage** : Vérification silencieuse
|
||||||
- Télécharge le bon fichier selon votre OS/architecture.
|
- **Notification** : Proposition de téléchargement si disponible
|
||||||
- Affiche une barre de progression stylée.
|
- **Installation** : Téléchargement avec barre de progression
|
||||||
- Lance la nouvelle version automatiquement !
|
- **Compatibilité** : Détection automatique OS/architecture
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💡 Suggestions & Feedback
|
## 🆘 Support et contribution
|
||||||
|
|
||||||
- Fenêtre dédiée pour envoyer vos idées ou questions par email.
|
### Signaler un problème
|
||||||
- Sécurisé via `.env`.
|
1. Utilisez l'onglet "Suggestion" dans l'application
|
||||||
- Gestion des erreurs/succès avec AlertManager.
|
2. Ou ouvrez une issue sur [Gitea](https://gitea.louismazin.ovh/LouisMazin/HoDA_Radio)
|
||||||
|
3. Incluez : OS, version Python, logs d'erreur, étapes de reproduction
|
||||||
|
|
||||||
---
|
### Contribuer
|
||||||
|
1. Forkez le dépôt
|
||||||
|
2. Créez une branche feature/fix
|
||||||
|
3. Testez vos modifications
|
||||||
|
4. Soumettez une pull request
|
||||||
|
|
||||||
## 🛠️ Ajouter vos fenêtres & widgets
|
### Règles de contribution
|
||||||
|
- Code Python PEP 8 compliant
|
||||||
- Créez une classe héritant de `QWidget`.
|
- Documentation des nouvelles fonctionnalités
|
||||||
- Ajoutez-la dans la barre d’onglets (`TabsWidget`).
|
- Tests unitaires pour la logique métier critique
|
||||||
- Abonnez-vous aux notifications pour la langue/le thème.
|
- Respect de l'architecture existante
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Dépendances
|
|
||||||
|
|
||||||
- **Python 3.10+ recommandé** (compatibilité testée avec 3.10/3.11).
|
|
||||||
- **PyQt6** : GUI moderne.
|
|
||||||
- **pyinstaller** : Build d’exécutables.
|
|
||||||
- **python-dotenv** : Variables d’environnement.
|
|
||||||
- **requests** : Requêtes HTTP (update).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Sécurité
|
|
||||||
|
|
||||||
- **Ne versionnez jamais `.env`** (déjà dans `.gitignore`).
|
|
||||||
- Utilisez un mot de passe d’application pour Gmail.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📝 Licence
|
## 📝 Licence
|
||||||
|
|
||||||
Attribution License — voir le fichier local [`LICENSE`](https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate/src/branch/main/LICENSE) pour le texte complet.
|
**Attribution License** - voir [LICENSE](LICENSE) pour les détails complets.
|
||||||
Toute utilisation ou distribution doit inclure une attribution visible à l'auteur : LouisMazin.
|
|
||||||
|
Toute utilisation, reproduction ou distribution doit inclure une attribution visible à l'auteur : **LouisMazin**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 Contribution
|
## 🙏 Remerciements
|
||||||
|
|
||||||
1. Forkez le dépôt
|
- **PyQt6** pour le framework d'interface
|
||||||
2. Créez une branche
|
- **pydicom** pour la manipulation des fichiers médicaux
|
||||||
3. Proposez vos modifications via pull request
|
- **Communauté open source** pour les contributions et retours
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🆘 Support
|
**HoDA Radio - Protégez la confidentialité, préservez l'utilité médicale** 🚀
|
||||||
|
|
||||||
- Ouvrez une issue sur le dépôt
|
|
||||||
- Précisez votre OS, version Python, logs d’erreur
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Ce template est fait pour vous faire gagner du temps et coder avec plaisir !
|
|
||||||
Testez-le, améliorez-le, partagez-le 🚀**
|
|
@ -24,8 +24,16 @@ class MainWindow(QMainWindow):
|
|||||||
self.observer_manager.subscribe(NotificationType.THEME, self.update_theme)
|
self.observer_manager.subscribe(NotificationType.THEME, self.update_theme)
|
||||||
self.observer_manager.subscribe(NotificationType.DICOM, self.goto_dicom_window)
|
self.observer_manager.subscribe(NotificationType.DICOM, self.goto_dicom_window)
|
||||||
self.is_maximizing: bool = False
|
self.is_maximizing: bool = False
|
||||||
self.current_size: QSize
|
|
||||||
self.previous_size: QSize
|
# Initialiser les attributs de taille AVANT setup_ui
|
||||||
|
app: Optional[QApplication] = QApplication.instance()
|
||||||
|
size: QSize = app.primaryScreen().size()
|
||||||
|
self.settings_manager.minScreenSize = min(size.height(), size.width())
|
||||||
|
|
||||||
|
# Initialiser les tailles par défaut
|
||||||
|
window_size: dict = self.settings_manager.get_window_size()
|
||||||
|
self.current_size: QSize = QSize(window_size["width"], window_size["height"])
|
||||||
|
self.previous_size: QSize = QSize(window_size["width"], window_size["height"])
|
||||||
|
|
||||||
# UI elements
|
# UI elements
|
||||||
self.side_menu: TabsWidget
|
self.side_menu: TabsWidget
|
||||||
@ -33,13 +41,21 @@ class MainWindow(QMainWindow):
|
|||||||
self.suggestion_window: SuggestionWindow
|
self.suggestion_window: SuggestionWindow
|
||||||
self.footer_label: QLabel # Ajout d'un attribut pour le footer
|
self.footer_label: QLabel # Ajout d'un attribut pour le footer
|
||||||
|
|
||||||
app: Optional[QApplication] = QApplication.instance()
|
|
||||||
size: QSize = app.primaryScreen().size()
|
|
||||||
self.settings_manager.minScreenSize = min(size.height(),size.width())
|
|
||||||
|
|
||||||
self.setMinimumSize(600, 400)
|
self.setMinimumSize(600, 400)
|
||||||
|
|
||||||
|
# Initialiser l'UI immédiatement (sera fait pendant le splash)
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
|
# Différer l'application des paramètres de fenêtre jusqu'à l'affichage réel
|
||||||
|
# (cela évite des bugs de taille pendant le préchargement)
|
||||||
|
self._window_state_applied = False
|
||||||
|
|
||||||
|
def showEvent(self, event):
|
||||||
|
"""Applique les paramètres de fenêtre lors du premier affichage"""
|
||||||
|
super().showEvent(event)
|
||||||
|
if not self._window_state_applied:
|
||||||
self.apply_saved_window_state()
|
self.apply_saved_window_state()
|
||||||
|
self._window_state_applied = True
|
||||||
|
|
||||||
def apply_saved_window_state(self) -> None:
|
def apply_saved_window_state(self) -> None:
|
||||||
"""Apply saved window size and maximized state"""
|
"""Apply saved window size and maximized state"""
|
||||||
|
@ -111,6 +111,8 @@ class ExportWindow(QWidget):
|
|||||||
def show_dicom_selection_popup(self):
|
def show_dicom_selection_popup(self):
|
||||||
dicom_list = self.export_manager.get_export_dicom_list()
|
dicom_list = self.export_manager.get_export_dicom_list()
|
||||||
if not dicom_list:
|
if not dicom_list:
|
||||||
|
# Afficher une alerte si aucune donnée n'est disponible
|
||||||
|
self.alert_manager.show_error("no_dicoms_to_export")
|
||||||
return
|
return
|
||||||
|
|
||||||
popup = QDialog(self)
|
popup = QDialog(self)
|
||||||
|
@ -294,10 +294,18 @@ class ImportWindow(QWidget):
|
|||||||
self.observer_manager.notify(NotificationType.DICOM)
|
self.observer_manager.notify(NotificationType.DICOM)
|
||||||
|
|
||||||
def select_file(self):
|
def select_file(self):
|
||||||
self.input_entry.setText(QFileDialog.getOpenFileName(self, self.language_manager.get_text("select_image"), get_current_dir(), "DICOMDIR Files (DICOMDIR) ;; DICOM Files (*.dcm)")[0])
|
path = QFileDialog.getOpenFileName(self, self.language_manager.get_text("select_image"), get_current_dir(), "DICOMDIR Files (DICOMDIR) ;; DICOM Files (*.dcm)")[0]
|
||||||
|
if path:
|
||||||
|
self.input_entry.setText(path)
|
||||||
|
else:
|
||||||
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
||||||
|
|
||||||
def select_folder(self):
|
def select_folder(self):
|
||||||
self.input_entry.setText(QFileDialog.getExistingDirectory(self, self.language_manager.get_text("select_folder"), get_current_dir()))
|
path = QFileDialog.getExistingDirectory(self, self.language_manager.get_text("select_folder"), get_current_dir())
|
||||||
|
if path:
|
||||||
|
self.input_entry.setText(path)
|
||||||
|
else:
|
||||||
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
||||||
|
|
||||||
def find_dicoms_from_files(self, path):
|
def find_dicoms_from_files(self, path):
|
||||||
if isinstance(path, tuple):
|
if isinstance(path, tuple):
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
|
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
|
||||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
||||||
from PyQt6.QtGui import QPixmap
|
from PyQt6.QtGui import QPixmap
|
||||||
from app.core.main_manager import MainManager
|
from app.core.main_manager import MainManager
|
||||||
from app.ui.widgets.loading_spinner import LoadingSpinner
|
from app.ui.widgets.loading_spinner import LoadingSpinner
|
||||||
import app.utils.paths as paths
|
import app.utils.paths as paths
|
||||||
|
|
||||||
class SplashScreen(QWidget):
|
class SplashScreen(QWidget):
|
||||||
finished = pyqtSignal()
|
finished = pyqtSignal(bool) # True si succès, False si échec/interruption
|
||||||
|
|
||||||
def __init__(self, duration=3000, parent=None):
|
def __init__(self, parent=None, preload_function=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.duration = duration
|
self.preload_function = preload_function
|
||||||
|
self.preload_result = True
|
||||||
|
|
||||||
self.main_manager = MainManager.get_instance()
|
self.main_manager = MainManager.get_instance()
|
||||||
self.theme_manager = self.main_manager.get_theme_manager()
|
self.theme_manager = self.main_manager.get_theme_manager()
|
||||||
self.settings_manager = self.main_manager.get_settings_manager()
|
self.settings_manager = self.main_manager.get_settings_manager()
|
||||||
|
self.language_manager = self.main_manager.get_language_manager()
|
||||||
|
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
self.setup_timer()
|
if self.preload_function:
|
||||||
|
self.start_preloading()
|
||||||
|
else:
|
||||||
|
# Pas de préchargement, fermer immédiatement
|
||||||
|
QTimer.singleShot(100, lambda: self.finished.emit(True))
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
# Configuration de la fenêtre
|
# Configuration de la fenêtre
|
||||||
@ -38,6 +43,12 @@ class SplashScreen(QWidget):
|
|||||||
self.load_splash_image()
|
self.load_splash_image()
|
||||||
layout.addWidget(self.image_label)
|
layout.addWidget(self.image_label)
|
||||||
|
|
||||||
|
# Texte de progression
|
||||||
|
self.progress_label = QLabel("Chargement...")
|
||||||
|
self.progress_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.progress_label.setStyleSheet("font-size: 14px; color: #666;")
|
||||||
|
layout.addWidget(self.progress_label)
|
||||||
|
|
||||||
# Spinner de chargement
|
# Spinner de chargement
|
||||||
self.spinner = LoadingSpinner(50, self)
|
self.spinner = LoadingSpinner(50, self)
|
||||||
spinner_layout = QVBoxLayout()
|
spinner_layout = QVBoxLayout()
|
||||||
@ -51,6 +62,38 @@ class SplashScreen(QWidget):
|
|||||||
# Centrer la fenêtre
|
# Centrer la fenêtre
|
||||||
self.center_on_screen()
|
self.center_on_screen()
|
||||||
|
|
||||||
|
def start_preloading(self):
|
||||||
|
"""Démarre le préchargement avec un délai pour permettre l'affichage du splash"""
|
||||||
|
# Laisser le temps au splash de s'afficher
|
||||||
|
QTimer.singleShot(200, self.do_preloading)
|
||||||
|
|
||||||
|
def do_preloading(self):
|
||||||
|
"""Effectue le préchargement dans le thread principal"""
|
||||||
|
try:
|
||||||
|
# Fonction callback pour mettre à jour le texte
|
||||||
|
def progress_callback(text):
|
||||||
|
self.progress_label.setText(text)
|
||||||
|
# Traiter les événements pour que l'UI se mette à jour
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
# Appeler la fonction de préchargement
|
||||||
|
success = self.preload_function(progress_callback)
|
||||||
|
self.preload_result = success
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.preload_result = False
|
||||||
|
|
||||||
|
# Attendre un peu puis fermer
|
||||||
|
QTimer.singleShot(300, self.close_splash)
|
||||||
|
|
||||||
|
def close_splash(self):
|
||||||
|
"""Ferme le splash screen et émet le signal"""
|
||||||
|
if hasattr(self, 'spinner'):
|
||||||
|
self.spinner.stop()
|
||||||
|
self.finished.emit(self.preload_result)
|
||||||
|
self.close()
|
||||||
|
|
||||||
def load_splash_image(self):
|
def load_splash_image(self):
|
||||||
"""Charge l'image splash depuis la config"""
|
"""Charge l'image splash depuis la config"""
|
||||||
try:
|
try:
|
||||||
@ -111,20 +154,6 @@ class SplashScreen(QWidget):
|
|||||||
y = (screen_geometry.height() - self.height()) // 2
|
y = (screen_geometry.height() - self.height()) // 2
|
||||||
self.move(x, y)
|
self.move(x, y)
|
||||||
|
|
||||||
def setup_timer(self):
|
|
||||||
"""Configure le timer pour fermer automatiquement le splash screen"""
|
|
||||||
self.timer = QTimer()
|
|
||||||
self.timer.timeout.connect(self.close_splash)
|
|
||||||
self.timer.start(self.duration)
|
|
||||||
|
|
||||||
def close_splash(self):
|
|
||||||
"""Ferme le splash screen et émet le signal"""
|
|
||||||
if hasattr(self, 'spinner'):
|
|
||||||
self.spinner.stop()
|
|
||||||
self.timer.stop()
|
|
||||||
self.finished.emit()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def show_splash(self):
|
def show_splash(self):
|
||||||
"""Affiche le splash screen"""
|
"""Affiche le splash screen"""
|
||||||
self.show()
|
self.show()
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 53 KiB |
Binary file not shown.
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
@ -73,5 +73,10 @@
|
|||||||
"error_loading_image": "Error loading image : {x}.",
|
"error_loading_image": "Error loading image : {x}.",
|
||||||
"export_metadata_error": "Error exporting metadata. Some files may have been exported.",
|
"export_metadata_error": "Error exporting metadata. Some files may have been exported.",
|
||||||
"export_partial_success": "Export partially successful. Some files could not be exported.",
|
"export_partial_success": "Export partially successful. Some files could not be exported.",
|
||||||
"choose_export_destination": "Choose export destination folder"
|
"choose_export_destination": "Choose export destination folder",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"checking_updates": "Checking for updates...",
|
||||||
|
"initializing": "Initializing...",
|
||||||
|
"loading_complete": "Loading complete",
|
||||||
|
"no_dicoms_to_export": "No DICOM files available for export. Please import DICOM files first."
|
||||||
}
|
}
|
@ -73,5 +73,10 @@
|
|||||||
"error_loading_image": "Error al cargar la imagen: {x}.",
|
"error_loading_image": "Error al cargar la imagen: {x}.",
|
||||||
"export_metadata_error": "Error al exportar metadatos. Algunos archivos pueden haber sido exportados.",
|
"export_metadata_error": "Error al exportar metadatos. Algunos archivos pueden haber sido exportados.",
|
||||||
"export_partial_success": "Exportación parcialmente exitosa. Algunos archivos no pudieron ser exportados.",
|
"export_partial_success": "Exportación parcialmente exitosa. Algunos archivos no pudieron ser exportados.",
|
||||||
"choose_export_destination": "Elige la carpeta de destino para la exportación"
|
"choose_export_destination": "Elige la carpeta de destino para la exportación",
|
||||||
|
"loading": "Cargando...",
|
||||||
|
"checking_updates": "Verificando actualizaciones...",
|
||||||
|
"initializing": "Inicializando...",
|
||||||
|
"loading_complete": "Carga completa",
|
||||||
|
"no_dicoms_to_export": "No hay archivos DICOM disponibles para exportar. Por favor, importe archivos DICOM primero."
|
||||||
}
|
}
|
@ -73,5 +73,10 @@
|
|||||||
"error_loading_image": "Erreur lors du chargement de l'image : {x}.",
|
"error_loading_image": "Erreur lors du chargement de l'image : {x}.",
|
||||||
"export_metadata_error": "Erreur lors de l'exportation des métadonnées. Certains fichiers ont pu être exportés.",
|
"export_metadata_error": "Erreur lors de l'exportation des métadonnées. Certains fichiers ont pu être exportés.",
|
||||||
"export_partial_success": "Exportation partiellement réussie. Certains fichiers n'ont pas pu être exportés.",
|
"export_partial_success": "Exportation partiellement réussie. Certains fichiers n'ont pas pu être exportés.",
|
||||||
"choose_export_destination": "Choisissez le dossier de destination pour l'exportation"
|
"choose_export_destination": "Choisissez le dossier de destination pour l'exportation",
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"checking_updates": "Vérification des mises à jour...",
|
||||||
|
"initializing": "Initialisation de l'interface...",
|
||||||
|
"loading_complete": "Chargement terminé",
|
||||||
|
"no_dicoms_to_export": "Aucun fichier DICOM disponible pour l'exportation. Veuillez d'abord importer des fichiers DICOM."
|
||||||
}
|
}
|
79
main.py
79
main.py
@ -6,11 +6,54 @@ from app.ui.main_window import MainWindow
|
|||||||
from app.ui.windows.splash_screen import SplashScreen
|
from app.ui.windows.splash_screen import SplashScreen
|
||||||
from app.core.main_manager import MainManager
|
from app.core.main_manager import MainManager
|
||||||
|
|
||||||
|
# Variable globale pour stocker la fenêtre préchargée
|
||||||
|
preloaded_window = None
|
||||||
|
|
||||||
|
def preload_application(progress_callback):
|
||||||
|
"""
|
||||||
|
Fonction de préchargement qui s'exécute pendant l'affichage du splash screen
|
||||||
|
|
||||||
|
Args:
|
||||||
|
progress_callback: Fonction pour mettre à jour le texte de progression
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True si tout s'est bien passé, False sinon
|
||||||
|
"""
|
||||||
|
global preloaded_window
|
||||||
|
|
||||||
|
try:
|
||||||
|
main_manager = MainManager.get_instance()
|
||||||
|
language_manager = main_manager.get_language_manager()
|
||||||
|
update_manager = main_manager.get_update_manager()
|
||||||
|
|
||||||
|
# Étape 1: Vérification des mises à jour
|
||||||
|
progress_callback(language_manager.get_text("checking_updates"))
|
||||||
|
|
||||||
|
# Vérifier s'il y a une mise à jour
|
||||||
|
if update_manager.check_for_update():
|
||||||
|
# Une mise à jour est en cours de téléchargement, on ferme l'app
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Étape 2: Initialisation de la fenêtre principale
|
||||||
|
progress_callback(language_manager.get_text("initializing"))
|
||||||
|
|
||||||
|
# Créer la fenêtre principale avec tous ses composants
|
||||||
|
preloaded_window = MainWindow()
|
||||||
|
|
||||||
|
# Étape 3: Finalisation
|
||||||
|
progress_callback(language_manager.get_text("loading_complete"))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
global preloaded_window
|
||||||
|
|
||||||
main_manager: MainManager = MainManager.get_instance()
|
main_manager: MainManager = MainManager.get_instance()
|
||||||
theme_manager = main_manager.get_theme_manager()
|
theme_manager = main_manager.get_theme_manager()
|
||||||
settings_manager = main_manager.get_settings_manager()
|
settings_manager = main_manager.get_settings_manager()
|
||||||
update_manager = main_manager.get_update_manager()
|
|
||||||
|
|
||||||
app: QApplication = QApplication(sys.argv)
|
app: QApplication = QApplication(sys.argv)
|
||||||
app.setStyleSheet(theme_manager.get_sheet())
|
app.setStyleSheet(theme_manager.get_sheet())
|
||||||
@ -22,24 +65,38 @@ def main() -> int:
|
|||||||
use_splash = splash_image_path and paths.Path(splash_image_path).exists()
|
use_splash = splash_image_path and paths.Path(splash_image_path).exists()
|
||||||
|
|
||||||
if use_splash:
|
if use_splash:
|
||||||
# Créer et afficher le splash screen
|
# Créer et afficher le splash screen avec préchargement
|
||||||
splash = SplashScreen(duration=1500)
|
splash = SplashScreen(preload_function=preload_application)
|
||||||
splash.show_splash()
|
splash.show_splash()
|
||||||
|
|
||||||
# Connecter le signal finished pour créer et afficher la fenêtre principale
|
# Connecter le signal finished pour afficher la fenêtre principale préchargée
|
||||||
def show_main_window():
|
def on_splash_finished(success):
|
||||||
if update_manager.check_for_update():
|
global preloaded_window
|
||||||
|
if not success:
|
||||||
|
# Le préchargement a échoué ou une mise à jour est en cours
|
||||||
app.quit()
|
app.quit()
|
||||||
return
|
return
|
||||||
window: MainWindow = MainWindow()
|
|
||||||
|
# Afficher la fenêtre préchargée
|
||||||
|
if preloaded_window:
|
||||||
|
preloaded_window.show()
|
||||||
|
else:
|
||||||
|
# Fallback si le préchargement a échoué
|
||||||
|
window = MainWindow()
|
||||||
window.show()
|
window.show()
|
||||||
|
|
||||||
splash.finished.connect(show_main_window)
|
splash.finished.connect(on_splash_finished)
|
||||||
else:
|
else:
|
||||||
# Pas de splash screen, vérifier les mises à jour puis afficher la fenêtre principale
|
# Pas de splash screen, exécuter le préchargement directement
|
||||||
if update_manager.check_for_update():
|
def dummy_progress(text):
|
||||||
|
pass # Pas de callback de progression sans splash
|
||||||
|
|
||||||
|
success = preload_application(dummy_progress)
|
||||||
|
if not success:
|
||||||
return 0
|
return 0
|
||||||
window: MainWindow = MainWindow()
|
|
||||||
|
# Utiliser la fenêtre préchargée ou en créer une nouvelle
|
||||||
|
window = preloaded_window if preloaded_window else MainWindow()
|
||||||
window.show()
|
window.show()
|
||||||
|
|
||||||
return app.exec()
|
return app.exec()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user