clean app
This commit is contained in:
parent
51ef3b40ec
commit
463dfa9508
@ -1,8 +1,5 @@
|
||||
PYTHON_PATH=path_to_python
|
||||
PYTHON_PATH=python
|
||||
EMAIL_ADDRESS=example@gmail.com
|
||||
EMAIL_PASSWORD=ztgzegzeg
|
||||
EMAIL_SMTP_SERVER=smtp.gmail.com
|
||||
EMAIL_SMTP_PORT=587
|
||||
LICENSE_API_URL=url
|
||||
PURCHASE_URL=url
|
||||
LICENSE_API_KEY=key
|
||||
@ -1,220 +0,0 @@
|
||||
import hashlib
|
||||
import platform
|
||||
import uuid
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
from PyQt6.QtCore import QObject, pyqtSignal
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import app.utils.paths as paths
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(paths.resource_path(".env"))
|
||||
|
||||
class LicenseManager(QObject):
|
||||
"""Gestionnaire de licences avec liaison matérielle"""
|
||||
|
||||
license_activated = pyqtSignal(str) # Signal émis lors de l'activation
|
||||
license_expired = pyqtSignal()
|
||||
|
||||
def __init__(self, settings_manager):
|
||||
super().__init__()
|
||||
self.settings_manager = settings_manager
|
||||
self.licensing_enabled = settings_manager.get_config("enable_licensing")
|
||||
|
||||
# Si le système de licence est désactivé, initialiser en mode gratuit
|
||||
if not self.licensing_enabled:
|
||||
self.hardware_id = None
|
||||
self.license_key = ""
|
||||
self.license_data = None
|
||||
return
|
||||
|
||||
# Charger l'URL de l'API depuis .env
|
||||
self.api_url = os.getenv("LICENSE_API_URL")
|
||||
if not self.api_url:
|
||||
raise ValueError("LICENSE_API_URL non définie dans le fichier .env")
|
||||
|
||||
self.hardware_id = self._get_hardware_id()
|
||||
|
||||
# Charger la licence sauvegardée
|
||||
self.license_key = settings_manager.settings.value("license_key", "")
|
||||
self.license_data = None
|
||||
|
||||
if self.license_key:
|
||||
self._load_license_data()
|
||||
|
||||
def _get_hardware_id(self) -> str:
|
||||
"""Génère un identifiant unique basé sur le matériel"""
|
||||
# Combine plusieurs éléments matériels pour un ID unique
|
||||
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
|
||||
for elements in range(0, 2*6, 2)][::-1])
|
||||
system = platform.system()
|
||||
machine = platform.machine()
|
||||
processor = platform.processor()
|
||||
|
||||
# Créer un hash unique
|
||||
unique_string = f"{mac}{system}{machine}{processor}"
|
||||
return hashlib.sha256(unique_string.encode()).hexdigest()
|
||||
|
||||
def get_hardware_id(self) -> str:
|
||||
"""Retourne le hardware ID pour l'afficher à l'utilisateur"""
|
||||
return self.hardware_id
|
||||
|
||||
def activate_license(self, license_key: str) -> Dict:
|
||||
"""
|
||||
Active une licence avec le serveur
|
||||
|
||||
Returns:
|
||||
dict: {"success": bool, "message": str, "data": dict}
|
||||
"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/activate",
|
||||
json={
|
||||
"license_key": license_key,
|
||||
"hardware_id": self.hardware_id,
|
||||
"app_version": self.settings_manager.get_config("app_version"),
|
||||
"platform": platform.system()
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
if response.status_code == 200 and data.get("success"):
|
||||
# Sauvegarder la licence
|
||||
self.license_key = license_key
|
||||
self.license_data = data.get("license_data", {})
|
||||
self.settings_manager.settings.setValue("license_key", license_key)
|
||||
self.settings_manager.settings.setValue("license_data", self.license_data)
|
||||
|
||||
self.license_activated.emit(license_key)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Licence activée avec succès",
|
||||
"data": self.license_data
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": data.get("message", "Erreur d'activation"),
|
||||
"data": None
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Erreur de connexion au serveur: {str(e)}",
|
||||
"data": None
|
||||
}
|
||||
|
||||
def verify_license(self) -> bool:
|
||||
"""Vérifie la validité de la licence avec le serveur"""
|
||||
if not self.license_key:
|
||||
return False
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/verify",
|
||||
json={
|
||||
"license_key": self.license_key,
|
||||
"hardware_id": self.hardware_id
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
if response.status_code == 200 and data.get("valid"):
|
||||
self.license_data = data.get("license_data", {})
|
||||
self.settings_manager.settings.setValue("license_data", self.license_data)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
# En cas d'erreur réseau, utiliser les données en cache
|
||||
return self._verify_offline()
|
||||
|
||||
def _verify_offline(self) -> bool:
|
||||
"""Vérification hors ligne basique"""
|
||||
if not self.license_key or not self.license_data:
|
||||
return False
|
||||
|
||||
# Vérifier que le hardware_id correspond
|
||||
if self.license_data.get("hardware_id") != self.hardware_id:
|
||||
return False
|
||||
|
||||
# Vérifier la date d'expiration si applicable
|
||||
expires_at = self.license_data.get("expires_at")
|
||||
if expires_at:
|
||||
expiry_date = datetime.fromisoformat(expires_at)
|
||||
if datetime.now() > expiry_date:
|
||||
self.license_expired.emit()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _load_license_data(self):
|
||||
"""Charge les données de licence depuis les settings"""
|
||||
self.license_data = self.settings_manager.settings.value("license_data", {})
|
||||
|
||||
def is_activated(self) -> bool:
|
||||
"""Vérifie si l'application est activée"""
|
||||
# Si le système de licence est désactivé, toujours retourner False
|
||||
if not self.licensing_enabled:
|
||||
return False
|
||||
|
||||
return bool(self.license_key) and self.verify_license()
|
||||
|
||||
def get_license_type(self) -> str:
|
||||
"""Retourne le type de licence (free, premium, etc.)"""
|
||||
# Si le système de licence est désactivé, retourner "free"
|
||||
if not self.licensing_enabled:
|
||||
return None
|
||||
|
||||
if not self.license_data:
|
||||
return None
|
||||
return self.license_data.get("type", None)
|
||||
|
||||
def is_feature_available(self, feature_id: str) -> bool:
|
||||
"""
|
||||
Vérifie si une fonctionnalité est disponible
|
||||
|
||||
Args:
|
||||
feature_id: Identifiant de la fonctionnalité (ex: "advanced_export")
|
||||
"""
|
||||
# Si le système de licence est désactivé, toutes les fonctionnalités sont disponibles
|
||||
if not self.licensing_enabled:
|
||||
return True
|
||||
|
||||
# Si pas de licence, vérifier dans la config des features gratuites
|
||||
if not self.is_activated():
|
||||
free_features = self.settings_manager.get_config("features_by_license").get(None, [])
|
||||
return feature_id in free_features
|
||||
|
||||
# Vérifier les features autorisées par la licence
|
||||
license_type = self.get_license_type()
|
||||
features_by_type = self.settings_manager.get_config("features_by_license")
|
||||
allowed_features = features_by_type.get(license_type, [])
|
||||
|
||||
return feature_id in allowed_features
|
||||
|
||||
def get_license_info(self) -> Dict:
|
||||
"""Retourne les informations de la licence"""
|
||||
if not self.license_data:
|
||||
return {
|
||||
"type": None,
|
||||
"status": "inactive",
|
||||
"expires_at": None
|
||||
}
|
||||
|
||||
return {
|
||||
"type": self.get_license_type(),
|
||||
"status": "active" if self.is_activated() else "inactive",
|
||||
"expires_at": self.license_data.get("expires_at"),
|
||||
"email": self.license_data.get("email"),
|
||||
"activated_at": self.license_data.get("activated_at")
|
||||
}
|
||||
@ -4,7 +4,6 @@ from app.core.theme_manager import ThemeManager
|
||||
from app.core.settings_manager import SettingsManager
|
||||
from app.core.alert_manager import AlertManager
|
||||
from app.core.update_manager import UpdateManager
|
||||
from app.core.license_manager import LicenseManager
|
||||
|
||||
from typing import Optional
|
||||
|
||||
@ -22,7 +21,6 @@ class MainManager:
|
||||
self.language_manager: LanguageManager = LanguageManager(self.settings_manager)
|
||||
self.alert_manager: AlertManager = AlertManager(self.language_manager, self.theme_manager)
|
||||
self.update_manager: UpdateManager = UpdateManager(self.settings_manager, self.language_manager, self.alert_manager)
|
||||
self.license_manager: LicenseManager = LicenseManager(self.settings_manager)
|
||||
@classmethod
|
||||
def get_instance(cls) -> 'MainManager':
|
||||
if cls._instance is None:
|
||||
@ -46,6 +44,3 @@ class MainManager:
|
||||
|
||||
def get_update_manager(self) -> UpdateManager:
|
||||
return self.update_manager
|
||||
|
||||
def get_license_manager(self) -> LicenseManager:
|
||||
return self.license_manager
|
||||
@ -1,6 +1,9 @@
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import app.utils.paths as paths
|
||||
import os, json
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
|
||||
class Theme:
|
||||
def __init__(self, name: str, colors: Dict[str, str]) -> None:
|
||||
@ -10,212 +13,43 @@ class Theme:
|
||||
def get_color(self, element: str) -> str:
|
||||
return self.colors.get(element, "#FFFFFF")
|
||||
|
||||
class ThemeManager:
|
||||
|
||||
class ThemeManager:
|
||||
def __init__(self) -> None:
|
||||
theme_path: str = os.path.join(paths.get_data_dir(), "themes")
|
||||
base_theme_path: str = os.path.join(theme_path, "base.qss")
|
||||
|
||||
with open(base_theme_path, "r", encoding="utf-8") as theme_file:
|
||||
self.base_sheet: str = theme_file.read()
|
||||
|
||||
self.themes: List[Theme] = []
|
||||
for theme_file in os.listdir(theme_path):
|
||||
if theme_file.endswith(".json"):
|
||||
with open(os.path.join(theme_path, theme_file), 'r', encoding='utf-8') as f:
|
||||
theme_data: Dict[str, Any] = json.load(f)
|
||||
theme: Theme = Theme(theme_data["theme_name"], theme_data["colors"])
|
||||
self.themes.append(theme)
|
||||
for theme_file in sorted(os.listdir(theme_path)):
|
||||
if not theme_file.endswith(".json"):
|
||||
continue
|
||||
|
||||
with open(os.path.join(theme_path, theme_file), "r", encoding="utf-8") as file:
|
||||
theme_data: Dict[str, Any] = json.load(file)
|
||||
self.themes.append(Theme(theme_data["theme_name"], theme_data["colors"]))
|
||||
|
||||
self.current_theme: Theme = self.themes[0]
|
||||
|
||||
def set_theme(self, theme: str) -> None:
|
||||
if theme != self.current_theme.name:
|
||||
found_theme: Optional[Theme] = next((t for t in self.themes if t.name == theme), None)
|
||||
found_theme: Optional[Theme] = next(
|
||||
(available_theme for available_theme in self.themes if available_theme.name == theme),
|
||||
None,
|
||||
)
|
||||
if found_theme:
|
||||
self.current_theme = found_theme
|
||||
|
||||
def get_theme(self) -> Theme:
|
||||
return self.current_theme
|
||||
|
||||
def get_themes(self) -> List[Theme]:
|
||||
return self.themes
|
||||
|
||||
def get_sheet(self) -> str:
|
||||
return f"""
|
||||
QWidget {{
|
||||
background-color: {self.current_theme.get_color("background_secondary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QLabel {{
|
||||
background-color: transparent;
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
font-size: 20px;
|
||||
}}
|
||||
QPushButton {{
|
||||
background-color: {self.current_theme.get_color("primary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: {self.current_theme.get_color("primary_hover_color")};
|
||||
}}
|
||||
QProgressBar {{
|
||||
border: 1px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 5px;
|
||||
background-color: {self.current_theme.get_color("background_secondary_color")};
|
||||
text-align: center;
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QProgressBar::chunk {{
|
||||
background-color: {self.current_theme.get_color("primary_color")};
|
||||
border-radius: 3px;
|
||||
}}
|
||||
QTextEdit {{
|
||||
border: 2px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
background-color: {self.current_theme.get_color("background_tertiary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QLineEdit {{
|
||||
border: 2px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
background-color: {self.current_theme.get_color("background_tertiary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QDateEdit {{
|
||||
border: 2px solid {self.current_theme.get_color("border_color")};
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
min-height: 30px;
|
||||
}}
|
||||
QDateEdit:hover {{
|
||||
border-color: {self.current_theme.get_color("primary_hover_color")};
|
||||
}}
|
||||
QComboBox {{
|
||||
border: 2px solid {self.current_theme.get_color("border_color")};
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
min-height: 30px;
|
||||
}}
|
||||
QComboBox QAbstractItemView {{
|
||||
border-radius: 8px;
|
||||
padding: 0px;
|
||||
outline: none;
|
||||
}}
|
||||
QComboBox QAbstractItemView::item {{
|
||||
padding: 12px 15px;
|
||||
margin: 0px;
|
||||
min-height: 20px;
|
||||
border: 1px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 8px;
|
||||
}}
|
||||
QComboBox QAbstractItemView::item:hover {{
|
||||
background-color: {self.current_theme.get_color("background_tertiary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QComboBox QAbstractItemView::item:selected {{
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QComboBox::drop-down {{
|
||||
border: none;
|
||||
background: transparent;
|
||||
}}
|
||||
QComboBox:hover {{
|
||||
border-color: {self.current_theme.get_color("primary_hover_color")};
|
||||
}}
|
||||
|
||||
#table_combobox {{
|
||||
border: 1px solid {self.current_theme.get_color("border_color")};
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
min-height: 20px;
|
||||
max-height: 28px;
|
||||
}}
|
||||
#table_combobox::drop-down {{
|
||||
border: none;
|
||||
background: transparent;
|
||||
}}
|
||||
#table_combobox::down-arrow {{
|
||||
image: none;
|
||||
}}
|
||||
#table_combobox QAbstractItemView {{
|
||||
border-radius: 4px;
|
||||
padding: 0px;
|
||||
outline: none;
|
||||
}}
|
||||
#table_combobox QAbstractItemView::item {{
|
||||
padding: 8px 10px;
|
||||
margin: 0px;
|
||||
min-height: 16px;
|
||||
border: 1px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 4px;
|
||||
}}
|
||||
#table_combobox QAbstractItemView::item:hover {{
|
||||
background-color: {self.current_theme.get_color("background_tertiary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
#table_combobox QAbstractItemView::item:selected {{
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
|
||||
QSlider::groove:horizontal {{
|
||||
border: 1px solid {self.current_theme.get_color("primary_color")};
|
||||
height: 10px;
|
||||
background: transparent;
|
||||
border-radius: 5px;
|
||||
}}
|
||||
QSlider::sub-page:horizontal {{
|
||||
background: {self.current_theme.get_color("primary_color")};
|
||||
border-radius: 5px;
|
||||
}}
|
||||
QSlider::add-page:horizontal {{
|
||||
background: {self.current_theme.get_color("background_tertiary_color")};
|
||||
border-radius: 5px;
|
||||
}}
|
||||
QSlider::handle:horizontal {{
|
||||
background: white;
|
||||
border: 2px solid {self.current_theme.get_color("primary_color")};
|
||||
width: 14px;
|
||||
margin: -4px 0;
|
||||
border-radius: 7px;
|
||||
}}
|
||||
QScrollBar:vertical {{
|
||||
border: none;
|
||||
background: {self.current_theme.get_color("background_tertiary_color")};
|
||||
width: 8px;
|
||||
margin: 0px;
|
||||
}}
|
||||
QScrollBar::handle:vertical {{
|
||||
background: {self.current_theme.get_color("primary_color")};
|
||||
border-radius: 4px;
|
||||
min-height: 20px;
|
||||
}}
|
||||
QScrollBar::handle:vertical:hover {{
|
||||
background: {self.current_theme.get_color("primary_hover_color")};
|
||||
}}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
|
||||
border: none;
|
||||
background: none;
|
||||
height: 0px;
|
||||
}}
|
||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {{
|
||||
background: none;
|
||||
}}
|
||||
#tab_bar {{
|
||||
background-color: {self.current_theme.get_color("background_color")};
|
||||
}}
|
||||
|
||||
#icon_label {{
|
||||
background: transparent;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
#text_label {{
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}}
|
||||
"""
|
||||
sheet: str = self.base_sheet
|
||||
for color_name, color_value in self.current_theme.colors.items():
|
||||
sheet = sheet.replace("{{" + color_name + "}}", color_value)
|
||||
return sheet
|
||||
@ -5,7 +5,6 @@ from app.core.main_manager import MainManager, NotificationType
|
||||
from app.ui.widgets.tabs_widget import TabsWidget, MenuDirection, ButtonPosition, BorderSide, TabSide, TextPosition
|
||||
from app.ui.windows.settings_window import SettingsWindow
|
||||
from app.ui.windows.suggestion_window import SuggestionWindow
|
||||
from app.ui.windows.activation_window import ActivationWindow
|
||||
import app.utils.paths as paths, shutil
|
||||
from typing import Optional
|
||||
|
||||
@ -259,11 +258,6 @@ class MainWindow(QMainWindow):
|
||||
self.settings_window = SettingsWindow(self)
|
||||
self.side_menu.add_widget(self.settings_window, self.language_manager.get_text("tab_settings"), paths.get_asset_svg_path("settings"), position=ButtonPosition.CENTER, text_position=TextPosition.BOTTOM)
|
||||
|
||||
# Ajouter la tab d'activation uniquement si le système de licence est activé
|
||||
if self.settings_manager.get_config("enable_licensing"):
|
||||
self.activation_window = ActivationWindow(self)
|
||||
self.side_menu.add_widget(self.activation_window, self.language_manager.get_text("tab_licensing"), paths.get_asset_svg_path("license"), position=ButtonPosition.END, text_position=TextPosition.BOTTOM)
|
||||
|
||||
self.setCentralWidget(self.side_menu)
|
||||
|
||||
def get_tab_widget(self):
|
||||
@ -277,5 +271,3 @@ class MainWindow(QMainWindow):
|
||||
# Mettre à jour les textes des onglets
|
||||
self.side_menu.update_button_text(0, self.language_manager.get_text("tab_suggestions"))
|
||||
self.side_menu.update_button_text(1, self.language_manager.get_text("tab_settings"))
|
||||
if self.settings_manager.get_config("enable_licensing"):
|
||||
self.side_menu.update_button_text(2, self.language_manager.get_text("tab_licensing"))
|
||||
@ -1,241 +0,0 @@
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QLineEdit, QPushButton, QFrame, QSizePolicy)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
from app.core.main_manager import MainManager, NotificationType
|
||||
from app.ui.widgets.loading_bar import LoadingBar
|
||||
import webbrowser
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import app.utils.paths as paths
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(paths.resource_path(".env"))
|
||||
|
||||
class ActivationThread(QThread):
|
||||
"""Thread pour l'activation afin de ne pas bloquer l'UI"""
|
||||
finished = pyqtSignal(dict)
|
||||
progress = pyqtSignal(int)
|
||||
|
||||
def __init__(self, license_manager, license_key):
|
||||
super().__init__()
|
||||
self.license_manager = license_manager
|
||||
self.license_key = license_key
|
||||
|
||||
def run(self):
|
||||
self.progress.emit(30)
|
||||
result = self.license_manager.activate_license(self.license_key)
|
||||
self.progress.emit(100)
|
||||
self.finished.emit(result)
|
||||
|
||||
class ActivationWindow(QWidget):
|
||||
"""Fenêtre d'activation de licence modernisée"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.main_manager = MainManager.get_instance()
|
||||
self.license_manager = self.main_manager.get_license_manager()
|
||||
self.language_manager = self.main_manager.get_language_manager()
|
||||
self.theme_manager = self.main_manager.get_theme_manager()
|
||||
self.alert_manager = self.main_manager.get_alert_manager()
|
||||
self.observer_manager = self.main_manager.get_observer_manager()
|
||||
self.settings_manager = self.main_manager.get_settings_manager()
|
||||
|
||||
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
||||
self.observer_manager.subscribe(NotificationType.THEME, self.update_theme)
|
||||
|
||||
self.activation_thread = None
|
||||
|
||||
self.setup_ui()
|
||||
self.update_license_status()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(20)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# === Section titre ===
|
||||
self.title_label = QLabel(self.language_manager.get_text("activate_license"))
|
||||
self.title_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
self.title_label.setWordWrap(True)
|
||||
layout.addWidget(self.title_label)
|
||||
|
||||
# === Spacer flexible ===
|
||||
layout.addStretch(1)
|
||||
|
||||
# === Section clé de licence ===
|
||||
key_layout = QHBoxLayout()
|
||||
|
||||
self.key_title = QLabel(self.language_manager.get_text("license_key_section"))
|
||||
self.key_title.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
key_layout.addWidget(self.key_title)
|
||||
|
||||
self.key_input = QLineEdit()
|
||||
self.key_input.setPlaceholderText("XXXX-XXXX-XXXX-XXXX-XXXX")
|
||||
self.key_input.textChanged.connect(self.format_license_key)
|
||||
self.key_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
key_layout.addWidget(self.key_input)
|
||||
|
||||
layout.addLayout(key_layout)
|
||||
# === Barre de progression ===
|
||||
self.progress_bar = LoadingBar(self.language_manager.get_text("loading"))
|
||||
self.progress_bar.setVisible(False)
|
||||
self.progress_bar.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
layout.addWidget(self.progress_bar)
|
||||
|
||||
# === Spacer flexible ===
|
||||
layout.addStretch(1)
|
||||
|
||||
# === Section statut ===
|
||||
self.status_label = QLabel()
|
||||
self.status_label.setWordWrap(True)
|
||||
self.status_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
# === Spacer flexible ===
|
||||
layout.addStretch(1)
|
||||
|
||||
# === Boutons d'action ===
|
||||
button_layout = QHBoxLayout()
|
||||
button_layout.setSpacing(10)
|
||||
|
||||
self.activate_btn = QPushButton(self.language_manager.get_text("activate"))
|
||||
self.activate_btn.clicked.connect(self.activate_license)
|
||||
self.activate_btn.setDefault(True)
|
||||
self.activate_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button_layout.addWidget(self.activate_btn)
|
||||
|
||||
self.buy_btn = QPushButton(self.language_manager.get_text("buy_license"))
|
||||
self.buy_btn.clicked.connect(self.open_purchase_page)
|
||||
self.buy_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button_layout.addWidget(self.buy_btn)
|
||||
|
||||
self.compare_btn = QPushButton(self.language_manager.get_text("compare_versions"))
|
||||
self.compare_btn.clicked.connect(self.show_features_comparison)
|
||||
self.compare_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button_layout.addWidget(self.compare_btn)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
def show_features_comparison(self):
|
||||
"""Affiche la comparaison des versions dans une alerte"""
|
||||
comparison_text = f"{self.language_manager.get_text('comparaisons')}\n\n"
|
||||
|
||||
for version in self.settings_manager.get_config("features_by_license").keys():
|
||||
comparison_text += f"{self.language_manager.get_text(version+'_version')}:\n"
|
||||
features = self.settings_manager.get_config("features_by_license").get(version, [])
|
||||
for feature in features:
|
||||
feature_desc = self.language_manager.get_text(feature)
|
||||
comparison_text += f" • {feature_desc}\n"
|
||||
comparison_text += "\n"
|
||||
|
||||
|
||||
self.alert_manager.show_info(comparison_text, parent=self)
|
||||
|
||||
def update_license_status(self):
|
||||
"""Met à jour l'affichage du statut de la licence"""
|
||||
if self.license_manager.is_activated():
|
||||
license_info = self.license_manager.get_license_info()
|
||||
license_type = license_info.get("type", "free").upper()
|
||||
|
||||
status_text = f"✓ {self.language_manager.get_text('license_active')}\n"
|
||||
status_text += f"{self.language_manager.get_text('license_type')}: {license_type}\n"
|
||||
if license_info.get("email"):
|
||||
status_text += f"{self.language_manager.get_text('license_email')}: {license_info['email']}\n"
|
||||
if license_info.get("expires_at"):
|
||||
status_text += f"{self.language_manager.get_text('license_expires')}: {license_info['expires_at']}"
|
||||
|
||||
self.status_label.setText(status_text)
|
||||
|
||||
self.key_input.setEnabled(False)
|
||||
self.activate_btn.setEnabled(False)
|
||||
else:
|
||||
status_text = f"{self.language_manager.get_text('no_license')}"
|
||||
self.status_label.setText(status_text)
|
||||
|
||||
def format_license_key(self, text):
|
||||
"""Formate automatiquement la clé de licence (XXXX-XXXX-...)"""
|
||||
text = text.replace("-", "").upper()
|
||||
formatted = "-".join([text[i:i+4] for i in range(0, len(text), 4)])
|
||||
if len(formatted) > 24:
|
||||
formatted = formatted[:24]
|
||||
self.key_input.blockSignals(True)
|
||||
self.key_input.setText(formatted)
|
||||
self.key_input.blockSignals(False)
|
||||
self.key_input.setCursorPosition(len(formatted))
|
||||
|
||||
def activate_license(self):
|
||||
"""Lance l'activation de la licence"""
|
||||
license_key = self.key_input.text().replace("-", "")
|
||||
|
||||
if len(license_key) < 16:
|
||||
self.alert_manager.show_error("invalid_license_key")
|
||||
return
|
||||
|
||||
# Disable inputs during activation
|
||||
self.activate_btn.setEnabled(False)
|
||||
self.key_input.setEnabled(False)
|
||||
self.buy_btn.setEnabled(False)
|
||||
|
||||
# Show progress bar with initial message
|
||||
self.progress_bar.set_label(self.language_manager.get_text("loading"))
|
||||
self.progress_bar.set_progress(0)
|
||||
self.progress_bar.setVisible(True)
|
||||
|
||||
# Start activation thread
|
||||
self.activation_thread = ActivationThread(self.license_manager, license_key)
|
||||
self.activation_thread.finished.connect(self.on_activation_finished)
|
||||
self.activation_thread.progress.connect(self.on_activation_progress)
|
||||
self.activation_thread.start()
|
||||
|
||||
def on_activation_progress(self, value):
|
||||
"""Update progress bar during activation"""
|
||||
self.progress_bar.set_progress(value)
|
||||
|
||||
def on_activation_finished(self, result):
|
||||
"""Callback quand l'activation est terminée"""
|
||||
# Hide progress bar
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
# Re-enable inputs
|
||||
self.activate_btn.setEnabled(True)
|
||||
self.key_input.setEnabled(True)
|
||||
self.buy_btn.setEnabled(True)
|
||||
|
||||
if result["success"]:
|
||||
# Show success message using AlertManager
|
||||
success_msg = result['message']
|
||||
if result.get("data"):
|
||||
success_msg += f"\n\n{self.language_manager.get_text('license_type')}: {result['data'].get('type', 'N/A')}"
|
||||
success_msg += f"\n{self.language_manager.get_text('license_email')}: {result['data'].get('email', 'N/A')}"
|
||||
|
||||
self.alert_manager.show_info(success_msg, parent=self)
|
||||
self.update_license_status()
|
||||
|
||||
# Clear the input field
|
||||
self.key_input.clear()
|
||||
else:
|
||||
# Show error message using AlertManager
|
||||
self.alert_manager.show_info(f"✗ {result['message']}", parent=self)
|
||||
|
||||
def open_purchase_page(self):
|
||||
"""Ouvre la page d'achat dans le navigateur"""
|
||||
purchase_url = os.getenv("PURCHASE_URL")
|
||||
if purchase_url:
|
||||
webbrowser.open(purchase_url)
|
||||
else:
|
||||
self.alert_manager.show_error("PURCHASE_URL non définie dans .env", parent=self)
|
||||
|
||||
def update_language(self):
|
||||
"""Met à jour tous les textes selon la nouvelle langue"""
|
||||
self.title_label.setText(self.language_manager.get_text("activate_license"))
|
||||
self.activate_btn.setText(self.language_manager.get_text("activate"))
|
||||
self.buy_btn.setText(self.language_manager.get_text("buy_license"))
|
||||
self.compare_btn.setText(self.language_manager.get_text("compare_versions"))
|
||||
self.progress_bar.set_label(self.language_manager.get_text("loading"))
|
||||
self.key_title.setText(self.language_manager.get_text("license_key_section"))
|
||||
self.update_license_status()
|
||||
|
||||
def update_theme(self):
|
||||
"""Met à jour le style selon le nouveau thème"""
|
||||
self.update_license_status()
|
||||
@ -62,7 +62,6 @@ class SuggestionWindow(QWidget):
|
||||
self.language_manager = self.main_manager.get_language_manager()
|
||||
self.settings_manager = self.main_manager.get_settings_manager()
|
||||
self.alert_manager = self.main_manager.get_alert_manager()
|
||||
self.license_manager = self.main_manager.get_license_manager()
|
||||
|
||||
self.observer_manager = self.main_manager.get_observer_manager()
|
||||
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
||||
@ -114,10 +113,6 @@ class SuggestionWindow(QWidget):
|
||||
|
||||
content = self.settings_manager.get_config('app_name').replace(' ', '_')
|
||||
|
||||
# Ajouter le préfixe "PRIORITAIRE" uniquement si le système de licence est activé ET que l'utilisateur a le support prioritaire
|
||||
if self.settings_manager.get_config("enable_licensing") and self.license_manager.is_feature_available("priority_support"):
|
||||
subject = "PRIORITAIRE - "+content
|
||||
else:
|
||||
subject = "Suggestion pour "+content
|
||||
|
||||
# Create and start email sender thread
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
from functools import wraps
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from app.core.main_manager import MainManager
|
||||
|
||||
def require_license(feature_id: str, show_upgrade_dialog: bool = True):
|
||||
"""
|
||||
Décorateur pour protéger une fonctionnalité premium
|
||||
|
||||
Usage:
|
||||
@require_license("advanced_export")
|
||||
def export_to_excel(self):
|
||||
# Code de la fonctionnalité premium
|
||||
"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
main_manager = MainManager.get_instance()
|
||||
license_manager = main_manager.get_license_manager()
|
||||
settings_manager = main_manager.get_settings_manager()
|
||||
|
||||
# Si le système de licence est désactivé, autoriser toutes les fonctionnalités
|
||||
if not settings_manager.get_config("enable_licensing"):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if not license_manager.is_feature_available(feature_id):
|
||||
if show_upgrade_dialog:
|
||||
show_upgrade_message(feature_id)
|
||||
return None
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def show_upgrade_message(feature_id: str):
|
||||
"""Affiche un message invitant à acheter la version premium"""
|
||||
main_manager = MainManager.get_instance()
|
||||
language_manager = main_manager.get_language_manager()
|
||||
settings_manager = main_manager.get_settings_manager()
|
||||
|
||||
# Si le système de licence est désactivé, ne rien afficher
|
||||
if not settings_manager.get_config("enable_licensing"):
|
||||
return
|
||||
|
||||
feature_name = settings_manager.get_config("feature_descriptions", {}).get(
|
||||
feature_id, feature_id
|
||||
)
|
||||
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Icon.Information)
|
||||
msg.setWindowTitle(language_manager.get_text("premium_feature"))
|
||||
msg.setText(language_manager.get_text("premium_feature_message").format(feature=feature_name))
|
||||
msg.setInformativeText(language_manager.get_text("upgrade_prompt"))
|
||||
|
||||
buy_btn = msg.addButton(language_manager.get_text("buy_now"), QMessageBox.ButtonRole.AcceptRole)
|
||||
activate_btn = msg.addButton(language_manager.get_text("activate_license"), QMessageBox.ButtonRole.ActionRole)
|
||||
msg.addButton(language_manager.get_text("cancel"), QMessageBox.ButtonRole.RejectRole)
|
||||
|
||||
msg.exec()
|
||||
|
||||
if msg.clickedButton() == buy_btn:
|
||||
import webbrowser
|
||||
webbrowser.open(settings_manager.get_config("purchase_url"))
|
||||
elif msg.clickedButton() == activate_btn:
|
||||
from app.ui.windows.activation_window import ActivationWindow
|
||||
activation_window = ActivationWindow()
|
||||
activation_window.exec()
|
||||
15
config.json
15
config.json
@ -6,18 +6,5 @@
|
||||
"icon_path": "data/assets/icon.ico",
|
||||
"splash_image": "splash",
|
||||
"main_script": "main.py",
|
||||
"git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate",
|
||||
"enable_licensing": true,
|
||||
"features_by_license": {
|
||||
"basic": [
|
||||
"support"
|
||||
],
|
||||
"premium": [
|
||||
"priority_support"
|
||||
],
|
||||
"enterprise": [
|
||||
"priority_support",
|
||||
"unlimited_key_uses"
|
||||
]
|
||||
}
|
||||
"git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate"
|
||||
}
|
||||
@ -5,8 +5,12 @@ import os
|
||||
from pathlib import Path
|
||||
from os import getenv
|
||||
|
||||
# --- Resolve paths relative to this spec file ---
|
||||
CONFIG_DIR = Path(SPECPATH).resolve()
|
||||
PROJECT_DIR = CONFIG_DIR.parent
|
||||
|
||||
# --- Load config.json ---
|
||||
config_path = Path("../config.json")
|
||||
config_path = PROJECT_DIR / "config.json"
|
||||
with config_path.open("r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
@ -22,16 +26,16 @@ icon = getenv("ICON_PATH", "")
|
||||
|
||||
# --- Data files to bundle ---
|
||||
datas = [
|
||||
("data/assets/*", "data/assets/"),
|
||||
("data/", "data/"),
|
||||
("config.json", "."),
|
||||
(".env", "."),
|
||||
(str(PROJECT_DIR / "data/assets/*"), "data/assets/"),
|
||||
(str(PROJECT_DIR / "data/"), "data/"),
|
||||
(str(PROJECT_DIR / "config.json"), "."),
|
||||
(str(PROJECT_DIR / ".env"), "."),
|
||||
]
|
||||
binaries = []
|
||||
|
||||
# --- Analysis ---
|
||||
a = Analysis(
|
||||
["main.py"],
|
||||
[str(PROJECT_DIR / "main.py")],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
@ -2,3 +2,4 @@ PyQt6
|
||||
pyinstaller
|
||||
python-dotenv
|
||||
requests
|
||||
dotenv
|
||||
@ -1,4 +0,0 @@
|
||||
<ns0:svg width="800px" height="800px" viewBox="0 0 24 24" xmlns:ns0="http://www.w3.org/2000/svg">
|
||||
<ns0:path fill-rule="evenodd" clip-rule="evenodd" d="M15.6807 14.5869C19.1708 14.5869 22 11.7692 22 8.29344C22 4.81767 19.1708 2 15.6807 2C12.1907 2 9.3615 4.81767 9.3615 8.29344C9.3615 9.90338 10.0963 11.0743 10.0963 11.0743L2.45441 18.6849C2.1115 19.0264 1.63143 19.9143 2.45441 20.7339L3.33616 21.6121C3.67905 21.9048 4.54119 22.3146 5.2466 21.6121L6.27531 20.5876C7.30403 21.6121 8.4797 21.0267 8.92058 20.4412C9.65538 19.4167 8.77362 18.3922 8.77362 18.3922L9.06754 18.0995C10.4783 19.5045 11.7128 18.6849 12.1537 18.0995C12.8885 17.075 12.1537 16.0505 12.1537 16.0505C11.8598 15.465 11.272 15.465 12.0067 14.7333L12.8885 13.8551C13.5939 14.4405 15.0439 14.5869 15.6807 14.5869Z M17.8853 8.29353C17.8853 9.50601 16.8984 10.4889 15.681 10.4889C14.4635 10.4889 13.4766 9.50601 13.4766 8.29353C13.4766 7.08105 14.4635 6.09814 15.681 6.09814C16.8984 6.09814 17.8853 7.08105 17.8853 8.29353Z" fill="#D1D1D6"/>
|
||||
|
||||
</ns0:svg>
|
||||
@ -30,38 +30,10 @@
|
||||
"update_details": "Update Details",
|
||||
"update_aborted": "Update aborted by user",
|
||||
"loading": "Loading...",
|
||||
"verifying_license": "Verifying license...",
|
||||
"checking_updates": "Checking for updates...",
|
||||
"initializing": "Initializing interface...",
|
||||
"loading_complete": "Loading complete",
|
||||
"activate_license": "Activate your license to unlock all features",
|
||||
"hardware_id_info": "This unique identifier is tied to your hardware. Share it with support if needed.",
|
||||
"hardware_id_copied": "Hardware ID copied to clipboard!",
|
||||
"license_key_section": "License Key :",
|
||||
"enter_license_key": "Enter your license key",
|
||||
"activate": "Activate",
|
||||
"buy_license": "Buy License",
|
||||
"comparaisons": "Version Comparison",
|
||||
"basic_version": "Basic Version",
|
||||
"premium_version": "Premium Version",
|
||||
"enterprise_version": "Enterprise Version",
|
||||
"support": "Support",
|
||||
"priority_support": "Priority support",
|
||||
"unlimited_key_uses": "Unlimited key uses",
|
||||
"license_active": "License active",
|
||||
"license_type": "Type",
|
||||
"license_email": "Email",
|
||||
"license_expires": "Expires on",
|
||||
"license_basic_mode": "Basic mode - Activate a license for more features",
|
||||
"invalid_license_key": "Invalid license key. It must contain at least 16 characters.",
|
||||
"premium_feature_message": "The feature '{feature}' requires a Premium or Enterprise license.",
|
||||
"upgrade_prompt": "Would you like to upgrade your license?",
|
||||
"buy_now": "Buy Now",
|
||||
"cancel": "Cancel",
|
||||
"activation_required": "Activation is required to continue.",
|
||||
"compare_versions": "Compare Versions",
|
||||
"no_license": "No License",
|
||||
"tab_suggestions": "Suggestions",
|
||||
"tab_settings": "Settings",
|
||||
"tab_licensing": "Licensing"
|
||||
"tab_settings": "Settings"
|
||||
}
|
||||
@ -30,39 +30,10 @@
|
||||
"update_details": "Détails de la mise à jour",
|
||||
"update_aborted": "Mise à jour annulée par l'utilisateur",
|
||||
"loading": "Chargement...",
|
||||
"verifying_license": "Vérification de la licence...",
|
||||
"checking_updates": "Vérification des mises à jour...",
|
||||
"initializing": "Initialisation de l'interface...",
|
||||
"loading_complete": "Chargement terminé",
|
||||
"activate_license": "Activez votre licence pour débloquer toutes les fonctionnalités",
|
||||
"hardware_id_info": "Cet identifiant unique est lié à votre matériel. Partagez-le avec le support si nécessaire.",
|
||||
"hardware_id_copied": "ID matériel copié dans le presse-papier !",
|
||||
"license_key_section": "Clé de licence :",
|
||||
"enter_license_key": "Entrez votre clé de licence",
|
||||
"activate": "Activer",
|
||||
"buy_license": "Acheter une licence",
|
||||
"comparaisons": "Comparaison des versions",
|
||||
"basic_version": "Version Basique",
|
||||
"premium_version": "Version Premium",
|
||||
"enterprise_version": "Version Enterprise",
|
||||
"support": "Support",
|
||||
"priority_support": "Support prioritaire",
|
||||
"unlimited_key_uses": "Utilisations de clé illimitées",
|
||||
"license_active": "Licence active",
|
||||
"license_type": "Type",
|
||||
"license_email": "Email",
|
||||
"license_expires": "Expire le",
|
||||
"license_basic_mode": "Mode basique - Activez une licence pour plus de fonctionnalités",
|
||||
"invalid_license_key": "Clé de licence invalide. Elle doit contenir au moins 16 caractères.",
|
||||
"premium_feature": "Fonctionnalité Premium",
|
||||
"premium_feature_message": "La fonctionnalité '{feature}' nécessite une licence Premium ou Enterprise.",
|
||||
"upgrade_prompt": "Souhaitez-vous mettre à niveau votre licence ?",
|
||||
"buy_now": "Acheter maintenant",
|
||||
"cancel": "Annuler",
|
||||
"activation_required": "L'activation est requise pour continuer.",
|
||||
"compare_versions": "Comparer les versions",
|
||||
"no_license": "Pas de licence",
|
||||
"tab_suggestions": "Suggestions",
|
||||
"tab_settings": "Paramètres",
|
||||
"tab_licensing": "Licence"
|
||||
"tab_settings": "Paramètres"
|
||||
}
|
||||
183
data/themes/base.qss
Normal file
183
data/themes/base.qss
Normal file
@ -0,0 +1,183 @@
|
||||
QWidget {
|
||||
background-color: {{background_secondary_color}};
|
||||
color: {{text_color}};
|
||||
}
|
||||
QLabel {
|
||||
background-color: transparent;
|
||||
color: {{text_color}};
|
||||
font-size: 20px;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: {{primary_color}};
|
||||
color: {{text_color}};
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: {{primary_hover_color}};
|
||||
}
|
||||
QProgressBar {
|
||||
border: 1px solid {{border_color}};
|
||||
border-radius: 5px;
|
||||
background-color: {{background_secondary_color}};
|
||||
text-align: center;
|
||||
color: {{text_color}};
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: {{primary_color}};
|
||||
border-radius: 3px;
|
||||
}
|
||||
QTextEdit {
|
||||
border: 2px solid {{border_color}};
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
background-color: {{background_tertiary_color}};
|
||||
color: {{text_color}};
|
||||
}
|
||||
QLineEdit {
|
||||
border: 2px solid {{border_color}};
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
background-color: {{background_tertiary_color}};
|
||||
color: {{text_color}};
|
||||
}
|
||||
QDateEdit {
|
||||
border: 2px solid {{border_color}};
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
min-height: 30px;
|
||||
}
|
||||
QDateEdit:hover {
|
||||
border-color: {{primary_hover_color}};
|
||||
}
|
||||
QComboBox {
|
||||
border: 2px solid {{border_color}};
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
min-height: 30px;
|
||||
}
|
||||
QComboBox QAbstractItemView {
|
||||
border-radius: 8px;
|
||||
padding: 0px;
|
||||
outline: none;
|
||||
}
|
||||
QComboBox QAbstractItemView::item {
|
||||
padding: 12px 15px;
|
||||
margin: 0px;
|
||||
min-height: 20px;
|
||||
border: 1px solid {{border_color}};
|
||||
border-radius: 8px;
|
||||
}
|
||||
QComboBox QAbstractItemView::item:hover {
|
||||
background-color: {{background_tertiary_color}};
|
||||
color: {{text_color}};
|
||||
}
|
||||
QComboBox QAbstractItemView::item:selected {
|
||||
color: {{text_color}};
|
||||
}
|
||||
QComboBox::drop-down {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
QComboBox:hover {
|
||||
border-color: {{primary_hover_color}};
|
||||
}
|
||||
|
||||
#table_combobox {
|
||||
border: 1px solid {{border_color}};
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
min-height: 20px;
|
||||
max-height: 28px;
|
||||
}
|
||||
#table_combobox::drop-down {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
#table_combobox::down-arrow {
|
||||
image: none;
|
||||
}
|
||||
#table_combobox QAbstractItemView {
|
||||
border-radius: 4px;
|
||||
padding: 0px;
|
||||
outline: none;
|
||||
}
|
||||
#table_combobox QAbstractItemView::item {
|
||||
padding: 8px 10px;
|
||||
margin: 0px;
|
||||
min-height: 16px;
|
||||
border: 1px solid {{border_color}};
|
||||
border-radius: 4px;
|
||||
}
|
||||
#table_combobox QAbstractItemView::item:hover {
|
||||
background-color: {{background_tertiary_color}};
|
||||
color: {{text_color}};
|
||||
}
|
||||
#table_combobox QAbstractItemView::item:selected {
|
||||
color: {{text_color}};
|
||||
}
|
||||
|
||||
QSlider::groove:horizontal {
|
||||
border: 1px solid {{primary_color}};
|
||||
height: 10px;
|
||||
background: transparent;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QSlider::sub-page:horizontal {
|
||||
background: {{primary_color}};
|
||||
border-radius: 5px;
|
||||
}
|
||||
QSlider::add-page:horizontal {
|
||||
background: {{background_tertiary_color}};
|
||||
border-radius: 5px;
|
||||
}
|
||||
QSlider::handle:horizontal {
|
||||
background: white;
|
||||
border: 2px solid {{primary_color}};
|
||||
width: 14px;
|
||||
margin: -4px 0;
|
||||
border-radius: 7px;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
border: none;
|
||||
background: {{background_tertiary_color}};
|
||||
width: 8px;
|
||||
margin: 0px;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background: {{primary_color}};
|
||||
border-radius: 4px;
|
||||
min-height: 20px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background: {{primary_hover_color}};
|
||||
}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||||
border: none;
|
||||
background: none;
|
||||
height: 0px;
|
||||
}
|
||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
||||
background: none;
|
||||
}
|
||||
#tab_bar {
|
||||
background-color: {{background_color}};
|
||||
}
|
||||
|
||||
#icon_label {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#text_label {
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
7
main.py
7
main.py
@ -25,13 +25,6 @@ def preload_application(progress_callback, splash=None):
|
||||
main_manager = MainManager.get_instance()
|
||||
language_manager = main_manager.get_language_manager()
|
||||
update_manager = main_manager.get_update_manager()
|
||||
license_manager = main_manager.get_license_manager()
|
||||
settings_manager = main_manager.get_settings_manager()
|
||||
|
||||
# Vérifier la licence uniquement si le système est activé
|
||||
if settings_manager.get_config("enable_licensing"):
|
||||
progress_callback(language_manager.get_text("verifying_license"))
|
||||
license_manager.verify_license()
|
||||
|
||||
progress_callback(language_manager.get_text("checking_updates"))
|
||||
|
||||
|
||||
@ -3,15 +3,10 @@ setlocal enabledelayedexpansion
|
||||
|
||||
REM === PATH SETUP ===
|
||||
set PARENT_DIR=%~dp0..
|
||||
set CONFIG_DIR=%PARENT_DIR%\config
|
||||
set CONFIG_FILE=%PARENT_DIR%\config.json
|
||||
set ICON_FILE=%PARENT_DIR%\data\assets\icon.png
|
||||
set ENV_FILE=%PARENT_DIR%\.env
|
||||
|
||||
REM Check if .env file exists
|
||||
if not exist "%ENV_FILE%" (
|
||||
echo [ERROR] .env file not found. Please copy .env.example to .env and configure it.
|
||||
exit /b 1
|
||||
)
|
||||
set ICON_FILE=%PARENT_DIR%\data\assets\icon.png
|
||||
|
||||
REM === Extract values from config.json ===
|
||||
for /f "delims=" %%i in ('powershell -NoProfile -Command ^
|
||||
@ -21,18 +16,22 @@ for /f "delims=" %%i in ('powershell -NoProfile -Command ^
|
||||
for /f "delims=" %%i in ('powershell -NoProfile -Command ^
|
||||
"Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty architecture"') do set ARCHITECTURE=%%i
|
||||
|
||||
REM === Extract python path from .env file ===
|
||||
for /f "usebackq tokens=2 delims==" %%i in (`findstr "PYTHON_PATH" "%ENV_FILE%"`) do set SYSTEM_PYTHON=%%i
|
||||
REM === Use the Python interpreter provided by the environment ===
|
||||
set SYSTEM_PYTHON=python
|
||||
if exist "%ENV_FILE%" (
|
||||
for /f "usebackq tokens=1,* delims==" %%A in ("%ENV_FILE%") do if /I "%%A"=="PYTHON_PATH" set "SYSTEM_PYTHON=%%B"
|
||||
)
|
||||
|
||||
set VENV_PATH=%PARENT_DIR%\WINenv_%ARCHITECTURE%
|
||||
set VENV_PATH=%CONFIG_DIR%\WINenv_%ARCHITECTURE%
|
||||
set EXE_NAME=%APP_NAME%.exe
|
||||
set PYTHON_IN_VENV=%VENV_PATH%\Scripts\python.exe
|
||||
set BUILD_DIR=%PARENT_DIR%\build
|
||||
set ZIP_FILE=%BUILD_DIR%\%APP_NAME%.zip
|
||||
|
||||
REM === Verify Python existence ===
|
||||
if not exist "%SYSTEM_PYTHON%" (
|
||||
echo [ERROR] Python not found at: %SYSTEM_PYTHON%
|
||||
"%SYSTEM_PYTHON%" --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Python interpreter not found in PATH.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
@ -41,7 +40,7 @@ if not exist "%VENV_PATH%\Scripts\activate.bat" (
|
||||
echo [INFO] Virtual environment not found. Creating...
|
||||
"%SYSTEM_PYTHON%" -m venv "%VENV_PATH%"
|
||||
"%PYTHON_IN_VENV%" -m pip install --upgrade pip
|
||||
"%PYTHON_IN_VENV%" -m pip install -r "%PARENT_DIR%\requirements.txt"
|
||||
"%PYTHON_IN_VENV%" -m pip install -r "%CONFIG_DIR%\requirements.txt"
|
||||
) else (
|
||||
echo [INFO] Virtual environment found.
|
||||
)
|
||||
@ -51,41 +50,9 @@ REM === Run PyInstaller ===
|
||||
--distpath "%BUILD_DIR%" ^
|
||||
--workpath "%BUILD_DIR%\dist" ^
|
||||
--clean ^
|
||||
"%PARENT_DIR%\BUILD.spec"
|
||||
"%CONFIG_DIR%\BUILD.spec"
|
||||
|
||||
REM === Clean build cache ===
|
||||
rmdir /s /q "%BUILD_DIR%\dist"
|
||||
|
||||
REM === Create ZIP ===
|
||||
echo [INFO] Creating ZIP archive...
|
||||
|
||||
set TEMP_ZIP_DIR=%BUILD_DIR%\temp_zip
|
||||
|
||||
REM Remove old temp dir if exists
|
||||
if exist "%TEMP_ZIP_DIR%" rmdir /s /q "%TEMP_ZIP_DIR%"
|
||||
mkdir "%TEMP_ZIP_DIR%"
|
||||
|
||||
REM Copy compiled app - tout le contenu du build sauf les ZIP existants
|
||||
move /Y "%BUILD_DIR%\%EXE_NAME%" "%TEMP_ZIP_DIR%\"
|
||||
|
||||
REM Copy config.json
|
||||
copy "%CONFIG_FILE%" "%TEMP_ZIP_DIR%\" /Y
|
||||
|
||||
REM Copy icon.png
|
||||
copy "%ICON_FILE%" "%TEMP_ZIP_DIR%\" /Y
|
||||
|
||||
REM Copy data/lang
|
||||
xcopy /E /I /Y "%PARENT_DIR%\data\lang" "%TEMP_ZIP_DIR%\lang"
|
||||
|
||||
REM Remove old ZIP if exists
|
||||
if exist "%ZIP_FILE%" del "%ZIP_FILE%"
|
||||
|
||||
REM Create ZIP
|
||||
powershell -NoProfile -Command "Add-Type -AssemblyName 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::CreateFromDirectory('%TEMP_ZIP_DIR%', '%ZIP_FILE%')"
|
||||
|
||||
REM Remove temp folder
|
||||
rmdir /s /q "%TEMP_ZIP_DIR%"
|
||||
|
||||
echo [INFO] ZIP created at: %ZIP_FILE%
|
||||
|
||||
endlocal
|
||||
|
||||
@ -4,14 +4,14 @@ set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PARENT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
CONFIG_DIR="$PARENT_DIR/config"
|
||||
CONFIG_FILE="$PARENT_DIR/config.json"
|
||||
ICON_FILE="$PARENT_DIR/data/assets/icon.png"
|
||||
ENV_FILE="$PARENT_DIR/.env"
|
||||
|
||||
# === Check .env ===
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "[ERROR] .env file not found. Please copy .env.example to .env and configure it."
|
||||
exit 1
|
||||
if [ -f "$PARENT_DIR/.env" ]; then
|
||||
set -a
|
||||
. "$PARENT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
# === Extract values from config.json ===
|
||||
@ -19,18 +19,18 @@ ICON_PATH=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['icon
|
||||
APP_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['app_name'])")
|
||||
ARCHITECTURE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['architecture'])")
|
||||
|
||||
# === Extract PYTHON_PATH from .env ===
|
||||
SYSTEM_PYTHON=$(grep '^PYTHON_PATH=' "$ENV_FILE" | cut -d '=' -f2-)
|
||||
# === Use the Python interpreter provided by the environment ===
|
||||
SYSTEM_PYTHON="${PYTHON_PATH:-python3}"
|
||||
|
||||
VENV_PATH="$PARENT_DIR/MACenv_$ARCHITECTURE"
|
||||
VENV_PATH="$CONFIG_DIR/MACenv_$ARCHITECTURE"
|
||||
APP_FILE="$APP_NAME.app"
|
||||
PYTHON_IN_VENV="$VENV_PATH/bin/python"
|
||||
BUILD_DIR="$PARENT_DIR/build"
|
||||
ZIP_FILE="$BUILD_DIR/$APP_NAME.zip"
|
||||
|
||||
# === Verify Python existence ===
|
||||
if [ ! -x "$SYSTEM_PYTHON" ]; then
|
||||
echo "[ERROR] Python not found at: $SYSTEM_PYTHON"
|
||||
if ! command -v "$SYSTEM_PYTHON" >/dev/null 2>&1; then
|
||||
echo "[ERROR] Python interpreter not found in PATH."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -39,7 +39,7 @@ if [ ! -f "$VENV_PATH/bin/activate" ]; then
|
||||
echo "[INFO] Virtual environment not found. Creating..."
|
||||
"$SYSTEM_PYTHON" -m venv "$VENV_PATH"
|
||||
"$PYTHON_IN_VENV" -m pip install --upgrade pip
|
||||
"$PYTHON_IN_VENV" -m pip install -r "$PARENT_DIR/requirements.txt"
|
||||
"$PYTHON_IN_VENV" -m pip install -r "$CONFIG_DIR/requirements.txt"
|
||||
else
|
||||
echo "[INFO] Virtual environment found."
|
||||
fi
|
||||
@ -49,7 +49,7 @@ fi
|
||||
--distpath "$BUILD_DIR" \
|
||||
--workpath "$BUILD_DIR/dist" \
|
||||
--clean \
|
||||
"$PARENT_DIR/BUILD.spec"
|
||||
"$CONFIG_DIR/BUILD.spec"
|
||||
|
||||
# === Clean build cache ===
|
||||
rm -rf "$BUILD_DIR/dist"
|
||||
|
||||
@ -3,30 +3,29 @@ setlocal enabledelayedexpansion
|
||||
|
||||
REM Set file paths
|
||||
set ROOT_DIR=%~dp0..
|
||||
set CONFIG_DIR=%ROOT_DIR%\config
|
||||
set CONFIG_FILE=%ROOT_DIR%\config.json
|
||||
set REQUIREMENTS=%ROOT_DIR%\requirements.txt
|
||||
set REQUIREMENTS=%CONFIG_DIR%\requirements.txt
|
||||
set ENV_FILE=%ROOT_DIR%\.env
|
||||
|
||||
REM Check if .env file exists
|
||||
if not exist "%ENV_FILE%" (
|
||||
echo [ERROR] .env file not found. Please copy .env.example to .env and configure it.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Extract config.json fields using PowerShell
|
||||
for /f "delims=" %%i in ('powershell -NoProfile -Command ^
|
||||
"Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty architecture"') do set ARCHITECTURE=%%i
|
||||
|
||||
REM Extract python path from .env file
|
||||
for /f "usebackq tokens=2 delims==" %%i in (`findstr "PYTHON_PATH" "%ENV_FILE%"`) do set PYTHON_EXEC=%%i
|
||||
REM Use the Python interpreter provided by the environment
|
||||
set PYTHON_EXEC=python
|
||||
if exist "%ENV_FILE%" (
|
||||
for /f "usebackq tokens=1,* delims==" %%A in ("%ENV_FILE%") do if /I "%%A"=="PYTHON_PATH" set "PYTHON_EXEC=%%B"
|
||||
)
|
||||
|
||||
REM Construct python executable path
|
||||
set ENV_NAME=WINenv_%ARCHITECTURE%
|
||||
set ENV_PATH=%ROOT_DIR%\%ENV_NAME%
|
||||
set ENV_PATH=%CONFIG_DIR%\%ENV_NAME%
|
||||
|
||||
if not exist "%PYTHON_EXEC%" (
|
||||
echo [ERROR] Python introuvable à: %PYTHON_EXEC%
|
||||
echo Veuillez vérifier votre .env ou le dossier d'installation.
|
||||
"%PYTHON_EXEC%" --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Python introuvable dans le PATH.
|
||||
echo Veuillez vérifier l'environnement Python fourni par Docker.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user